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}