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    at: std::collections::HashMap<tilo_actions::ParticipantId, Place>,
32    /// The latest landings in order, numbered, for surfaces to follow.
33    log: std::collections::VecDeque<(u64, tilo_actions::ParticipantId, Place)>,
34    next: u64,
35}
36
37impl<R: Resource> Default for Places<R> {
38    fn default() -> Self {
39        Self {
40            applied: Vec::new(),
41            at: std::collections::HashMap::new(),
42            log: std::collections::VecDeque::new(),
43            next: 0,
44        }
45    }
46}
47
48/// How many landings a surface that missed some can still catch up on.
49const LANDINGS_KEPT: usize = 64;
50
51/// What changed since bound controls last followed the state: `None` when
52/// nothing did.
53#[derive(Default)]
54struct Changes {
55    pending: Option<Delta>,
56}
57
58impl Changes {
59    fn note(&mut self, delta: Delta) {
60        match &mut self.pending {
61            Some(pending) => pending.merge(delta),
62            None => self.pending = Some(delta),
63        }
64    }
65}
66
67impl<R: Resource> Clone for Live<R> {
68    fn clone(&self) -> Self {
69        Self {
70            inner: Rc::clone(&self.inner),
71            changes: Rc::clone(&self.changes),
72            places: Rc::clone(&self.places),
73        }
74    }
75}
76
77impl<R: Resource> Live<R> {
78    pub(crate) fn new(reader: Reader<R>) -> Self {
79        Self {
80            inner: Rc::new(RefCell::new(reader)),
81            changes: Rc::default(),
82            places: Rc::default(),
83        }
84    }
85
86    /// Note that `action` was applied for `participant`; where it left them
87    /// is read once the state follows it ([`Self::place_landings`]).
88    pub(crate) fn note_applied(&self, participant: tilo_actions::ParticipantId, action: R::Action) {
89        self.places.borrow_mut().applied.push((participant, action));
90    }
91
92    /// Place every participant whose action the state now holds, where
93    /// [`Resource::lands`] says it left them.
94    #[inline(never)]
95    pub(crate) fn place_landings(&self) {
96        let applied = std::mem::take(&mut self.places.borrow_mut().applied);
97        if applied.is_empty() {
98            return;
99        }
100        let state = self.get();
101        for (participant, action) in applied {
102            if let Some(place) = R::lands(&state, &action) {
103                self.land(participant, place);
104            }
105        }
106    }
107
108    /// Place `participant` at `place`: where an effect they told where to
109    /// act acted ([`EffectCx::place`](super::EffectCx::place)).
110    #[inline(never)]
111    pub(crate) fn land(&self, participant: tilo_actions::ParticipantId, place: Place) {
112        let mut places = self.places.borrow_mut();
113        let number = places.next;
114        places.next = number.saturating_add(1);
115        places.at.insert(participant.clone(), place.clone());
116        places.log.push_back((number, participant, place));
117        if places.log.len() > LANDINGS_KEPT {
118            places.log.pop_front();
119        }
120    }
121
122    /// Where `participant`'s last action left them, as the resource places
123    /// it ([`Resource::lands`]): the paragraph an agent appended, the table
124    /// it inserted. `None` before they did anything that lands somewhere.
125    pub fn place_of(&self, participant: &tilo_actions::ParticipantId) -> Option<Place> {
126        self.places.borrow().at.get(participant).cloned()
127    }
128
129    /// The landings numbered `from` on, and the number to ask from next.
130    pub(crate) fn landings_since(
131        &self,
132        from: u64,
133    ) -> (Vec<(tilo_actions::ParticipantId, Place)>, u64) {
134        let places = self.places.borrow();
135        let landed = places
136            .log
137            .iter()
138            .filter(|(number, _, _)| *number >= from)
139            .map(|(_, participant, place)| (participant.clone(), place.clone()))
140            .collect();
141        (landed, places.next)
142    }
143
144    /// What changed since the last call, for the readers bound to the
145    /// state: `None` when nothing did.
146    pub(crate) fn take_change(&self) -> Option<Delta> {
147        self.changes.borrow_mut().pending.take()
148    }
149
150    /// The current state. Cheap: shares the materialised value.
151    ///
152    /// Keep it only as long as a frame needs it. A state still shared when
153    /// the document next changes cannot be updated in place, so the
154    /// framework reads the whole resource again instead; read through
155    /// [`with`](Self::with) or call this again when you need it.
156    pub fn get(&self) -> Rc<R::State> {
157        Rc::clone(self.inner.borrow().state())
158    }
159
160    /// Read the current state.
161    pub fn with<T>(&self, read: impl FnOnce(&R::State) -> T) -> T {
162        read(self.inner.borrow().state())
163    }
164
165    /// Why the document stopped reading as the resource, if it did: the
166    /// state is then the last one that read, and views show this instead.
167    pub fn unreadable(&self) -> Option<tilo_resource::Unreadable> {
168        self.inner.borrow().unreadable().cloned()
169    }
170
171    /// Increases whenever the state changes.
172    pub fn revision(&self) -> u64 {
173        self.inner.borrow().revision()
174    }
175
176    /// Whether the state is `doc` as it stands: no change arrived since the
177    /// framework last synced it.
178    pub fn follows(&self, doc: &Doc) -> bool {
179        self.inner.borrow().follows(doc)
180    }
181
182    /// Show the parts of a large resource `demand` names, reading those not
183    /// read yet from `doc` — the document the view was synced with, which in
184    /// History mode is the past version on screen. Returns whether the state
185    /// changed.
186    ///
187    /// # Errors
188    /// Returns [`ModelError`] when the document does not hold the resource.
189    #[inline(never)]
190    pub fn demand(&self, doc: &Doc, demand: Demand) -> Result<bool, ModelError>
191    where
192        R::State: Clone,
193    {
194        let changed = self.inner.borrow_mut().demand(doc, demand)?;
195        if changed {
196            self.changes.borrow_mut().note(Delta::everything());
197        }
198        Ok(changed)
199    }
200
201    /// Ask everything bound to the state to take it again, unchanged. After
202    /// an edit was refused or is waiting for confirmation, a surface that
203    /// previewed it optimistically is re-presented the stored state.
204    pub(crate) fn represent(&self) {
205        self.inner.borrow_mut().represent();
206        self.changes.borrow_mut().note(Delta::everything());
207    }
208
209    /// What views currently demand.
210    pub fn current_demand(&self) -> Demand {
211        self.inner.borrow().current_demand().clone()
212    }
213
214    /// Apply what changed in `doc` since the last sync, reading only what
215    /// changed. The framework does this before every frame; a surface calls
216    /// it after an immediate edit ([`Dispatcher::apply_now`](super::Dispatcher::apply_now))
217    /// to read its own result within the same event. Returns whether the
218    /// state changed.
219    ///
220    /// # Errors
221    /// Returns [`ModelError`] when the document no longer holds the resource.
222    #[inline(never)]
223    pub fn sync(&self, doc: &Doc) -> Result<bool, ModelError> {
224        let _profile = tilo_resource::profiling::Phase::start("framework.live_sync");
225        let mut reader = self.inner.borrow_mut();
226        let moved = reader.sync(doc)?;
227        if moved {
228            self.changes.borrow_mut().note(reader.last_delta().clone());
229        }
230        Ok(moved)
231    }
232}