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}