Skip to main content

open_gpui_canvas/persistence/
store.rs

1use crate::{
2    CanvasCommittedMutation, CanvasDocument, CanvasDocumentDiff, CanvasEditor, CanvasEvent,
3    CanvasPreparedMutation, CanvasRecordOperationBatch, CanvasRelationOperationBatch,
4    CanvasSnapshot, CanvasToolId, CanvasToolIntent, CanvasToolReducer, CanvasToolRegistry,
5    CanvasTransaction, DocumentError, tool::CanvasToolEffect,
6};
7use crate::{CanvasStore, CanvasStoreChange};
8use std::{convert::Infallible, error::Error, fmt};
9
10#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
11pub struct CanvasCheckpoint {
12    pub sequence: u64,
13    pub snapshot: CanvasSnapshot,
14}
15
16impl CanvasCheckpoint {
17    pub fn new(sequence: u64, document: &CanvasDocument) -> Self {
18        Self {
19            sequence,
20            snapshot: document.to_snapshot(),
21        }
22    }
23}
24
25#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
26pub struct CanvasLogEntry {
27    sequence: u64,
28    transaction: CanvasTransaction,
29    #[serde(default, alias = "record_operation_batch")]
30    committed_record_operation_batch: Option<CanvasRecordOperationBatch>,
31    #[serde(default)]
32    committed_relation_operation_batch: Option<CanvasRelationOperationBatch>,
33}
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum CanvasLogEntryKind {
37    CommittedMutation,
38    PartialCommittedMutation,
39    LegacyReplayTransaction,
40}
41
42impl CanvasLogEntry {
43    pub fn from_replay_transaction(
44        sequence: u64,
45        transaction: impl Into<CanvasTransaction>,
46    ) -> Self {
47        Self {
48            sequence,
49            transaction: transaction.into(),
50            committed_record_operation_batch: None,
51            committed_relation_operation_batch: None,
52        }
53    }
54
55    pub fn from_committed_mutation(sequence: u64, committed: &CanvasCommittedMutation) -> Self {
56        Self {
57            sequence,
58            transaction: committed.transaction().clone(),
59            committed_record_operation_batch: Some(committed.record_operation_batch(sequence)),
60            committed_relation_operation_batch: Some(committed.relation_operation_batch(sequence)),
61        }
62    }
63
64    pub fn sequence(&self) -> u64 {
65        self.sequence
66    }
67
68    pub fn transaction(&self) -> &CanvasTransaction {
69        &self.transaction
70    }
71
72    pub fn kind(&self) -> CanvasLogEntryKind {
73        match (
74            self.committed_record_operation_batch.is_some(),
75            self.committed_relation_operation_batch.is_some(),
76        ) {
77            (true, true) => CanvasLogEntryKind::CommittedMutation,
78            (true, false) | (false, true) => CanvasLogEntryKind::PartialCommittedMutation,
79            (false, false) => CanvasLogEntryKind::LegacyReplayTransaction,
80        }
81    }
82
83    pub fn committed_record_operations(&self) -> Option<&CanvasRecordOperationBatch> {
84        self.committed_record_operation_batch.as_ref()
85    }
86
87    pub fn committed_relation_operations(&self) -> Option<&CanvasRelationOperationBatch> {
88        self.committed_relation_operation_batch.as_ref()
89    }
90
91    pub fn is_legacy_replay_entry(&self) -> bool {
92        self.kind() == CanvasLogEntryKind::LegacyReplayTransaction
93    }
94}
95
96#[derive(Debug, Eq, PartialEq)]
97pub enum CanvasPersistenceError<E = Infallible> {
98    Store(E),
99    Document(DocumentError),
100    NonMonotonicLogSequence { previous: u64, found: u64 },
101}
102
103impl<E> fmt::Display for CanvasPersistenceError<E>
104where
105    E: fmt::Display,
106{
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            Self::Store(error) => write!(f, "canvas persistence store error: {error}"),
110            Self::Document(error) => fmt::Display::fmt(error, f),
111            Self::NonMonotonicLogSequence { previous, found } => write!(
112                f,
113                "canvas transaction log sequence `{found}` is not greater than previous sequence `{previous}`"
114            ),
115        }
116    }
117}
118
119impl<E> Error for CanvasPersistenceError<E> where E: fmt::Debug + fmt::Display {}
120
121impl<E> From<DocumentError> for CanvasPersistenceError<E> {
122    fn from(value: DocumentError) -> Self {
123        Self::Document(value)
124    }
125}
126
127pub type CanvasReplayError = CanvasPersistenceError<Infallible>;
128
129#[derive(Debug, Eq, PartialEq)]
130pub enum CanvasPersistentToolRegistryError<E = Infallible> {
131    MissingTool(CanvasToolId),
132    Persistence(CanvasPersistenceError<E>),
133}
134
135impl<E> From<CanvasPersistenceError<E>> for CanvasPersistentToolRegistryError<E> {
136    fn from(value: CanvasPersistenceError<E>) -> Self {
137        Self::Persistence(value)
138    }
139}
140
141impl<E> fmt::Display for CanvasPersistentToolRegistryError<E>
142where
143    E: fmt::Display,
144{
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        match self {
147            Self::MissingTool(id) => write!(f, "canvas custom tool `{id}` is not registered"),
148            Self::Persistence(error) => fmt::Display::fmt(error, f),
149        }
150    }
151}
152
153impl<E> Error for CanvasPersistentToolRegistryError<E>
154where
155    E: fmt::Debug + fmt::Display + 'static,
156{
157    fn source(&self) -> Option<&(dyn Error + 'static)> {
158        match self {
159            Self::MissingTool(_) => None,
160            Self::Persistence(error) => Some(error),
161        }
162    }
163}
164
165#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
166pub struct CanvasPersistenceCursor {
167    sequence: u64,
168}
169
170impl CanvasPersistenceCursor {
171    pub fn new(sequence: u64) -> Self {
172        Self { sequence }
173    }
174
175    pub fn sequence(&self) -> u64 {
176        self.sequence
177    }
178
179    pub fn next_sequence(&self) -> u64 {
180        self.sequence + 1
181    }
182
183    pub fn advance(&mut self) -> u64 {
184        self.sequence = self.next_sequence();
185        self.sequence
186    }
187}
188
189pub trait CanvasPersistenceStore {
190    type Error: fmt::Debug + fmt::Display;
191
192    fn load_checkpoint(&self) -> Result<Option<CanvasCheckpoint>, Self::Error>;
193
194    fn save_checkpoint(&mut self, checkpoint: CanvasCheckpoint) -> Result<(), Self::Error>;
195
196    fn append_log_entry(&mut self, entry: CanvasLogEntry) -> Result<(), Self::Error>;
197
198    fn load_log_entries(&self, after_sequence: u64) -> Result<Vec<CanvasLogEntry>, Self::Error>;
199
200    fn compact_log_entries(&mut self, through_sequence: u64) -> Result<(), Self::Error>;
201}
202
203pub fn replay_canvas_log(
204    checkpoint: Option<CanvasCheckpoint>,
205    log_entries: impl IntoIterator<Item = CanvasLogEntry>,
206) -> Result<CanvasDocument, CanvasReplayError> {
207    replay_checkpoint_and_log(checkpoint, log_entries)
208}
209
210pub fn load_canvas_document<S>(
211    store: &S,
212) -> Result<CanvasDocument, CanvasPersistenceError<S::Error>>
213where
214    S: CanvasPersistenceStore,
215{
216    let checkpoint = store
217        .load_checkpoint()
218        .map_err(CanvasPersistenceError::Store)?;
219    let after_sequence = checkpoint
220        .as_ref()
221        .map_or(0, |checkpoint| checkpoint.sequence);
222    let log_entries = store
223        .load_log_entries(after_sequence)
224        .map_err(CanvasPersistenceError::Store)?;
225
226    replay_checkpoint_and_log(checkpoint, log_entries)
227}
228
229pub fn load_canvas_persistence_cursor<S>(
230    store: &S,
231) -> Result<CanvasPersistenceCursor, CanvasPersistenceError<S::Error>>
232where
233    S: CanvasPersistenceStore,
234{
235    let checkpoint = store
236        .load_checkpoint()
237        .map_err(CanvasPersistenceError::Store)?;
238    let mut previous_sequence = checkpoint
239        .as_ref()
240        .map_or(0, |checkpoint| checkpoint.sequence);
241    let log_entries = store
242        .load_log_entries(previous_sequence)
243        .map_err(CanvasPersistenceError::Store)?;
244
245    for entry in log_entries {
246        if entry.sequence() <= previous_sequence {
247            return Err(CanvasPersistenceError::NonMonotonicLogSequence {
248                previous: previous_sequence,
249                found: entry.sequence(),
250            });
251        }
252
253        previous_sequence = entry.sequence();
254    }
255
256    Ok(CanvasPersistenceCursor::new(previous_sequence))
257}
258
259pub fn apply_persistent_transaction<S>(
260    editor: &mut CanvasEditor,
261    store: &mut S,
262    cursor: &mut CanvasPersistenceCursor,
263    transaction: CanvasTransaction,
264) -> Result<CanvasDocumentDiff, CanvasPersistenceError<S::Error>>
265where
266    S: CanvasPersistenceStore,
267{
268    let change =
269        apply_persistent_store_transaction(editor.store_mut(), store, cursor, transaction)?;
270    let diff = change
271        .as_ref()
272        .map_or_else(CanvasDocumentDiff::default, |change| change.diff().clone());
273    if change.is_some() {
274        editor.retain_selection_for_current_document();
275    }
276    Ok(diff)
277}
278
279pub fn apply_persistent_store_transaction<S>(
280    canvas_store: &mut CanvasStore,
281    store: &mut S,
282    cursor: &mut CanvasPersistenceCursor,
283    transaction: CanvasTransaction,
284) -> Result<Option<CanvasStoreChange>, CanvasPersistenceError<S::Error>>
285where
286    S: CanvasPersistenceStore,
287{
288    if transaction.is_empty() {
289        return Ok(None);
290    }
291
292    let prepared = canvas_store.prepare_transaction(transaction)?;
293    append_then_apply_prepared_mutation(
294        canvas_store,
295        store,
296        cursor,
297        prepared,
298        |canvas_store, prepared| canvas_store.apply_prepared_transaction(prepared),
299    )
300}
301
302pub fn undo_persistent_transaction<S>(
303    editor: &mut CanvasEditor,
304    store: &mut S,
305    cursor: &mut CanvasPersistenceCursor,
306) -> Result<bool, CanvasPersistenceError<S::Error>>
307where
308    S: CanvasPersistenceStore,
309{
310    let change = undo_persistent_store_transaction(editor.store_mut(), store, cursor)?;
311    if change.is_some() {
312        editor.retain_selection_for_current_document();
313    }
314    Ok(change.is_some())
315}
316
317pub fn undo_persistent_store_transaction<S>(
318    canvas_store: &mut CanvasStore,
319    store: &mut S,
320    cursor: &mut CanvasPersistenceCursor,
321) -> Result<Option<CanvasStoreChange>, CanvasPersistenceError<S::Error>>
322where
323    S: CanvasPersistenceStore,
324{
325    let Some(prepared) = canvas_store.prepare_undo()? else {
326        return Ok(None);
327    };
328    append_then_apply_prepared_mutation(
329        canvas_store,
330        store,
331        cursor,
332        prepared,
333        |canvas_store, prepared| canvas_store.apply_prepared_undo(prepared),
334    )
335}
336
337pub fn redo_persistent_transaction<S>(
338    editor: &mut CanvasEditor,
339    store: &mut S,
340    cursor: &mut CanvasPersistenceCursor,
341) -> Result<bool, CanvasPersistenceError<S::Error>>
342where
343    S: CanvasPersistenceStore,
344{
345    let change = redo_persistent_store_transaction(editor.store_mut(), store, cursor)?;
346    if change.is_some() {
347        editor.retain_selection_for_current_document();
348    }
349    Ok(change.is_some())
350}
351
352pub fn redo_persistent_store_transaction<S>(
353    canvas_store: &mut CanvasStore,
354    store: &mut S,
355    cursor: &mut CanvasPersistenceCursor,
356) -> Result<Option<CanvasStoreChange>, CanvasPersistenceError<S::Error>>
357where
358    S: CanvasPersistenceStore,
359{
360    let Some(prepared) = canvas_store.prepare_redo()? else {
361        return Ok(None);
362    };
363    append_then_apply_prepared_mutation(
364        canvas_store,
365        store,
366        cursor,
367        prepared,
368        |canvas_store, prepared| canvas_store.apply_prepared_redo(prepared),
369    )
370}
371
372pub(crate) fn apply_persistent_tool_effect<S>(
373    editor: &mut CanvasEditor,
374    store: &mut S,
375    cursor: &mut CanvasPersistenceCursor,
376    effect: CanvasToolEffect,
377) -> Result<(), CanvasPersistenceError<S::Error>>
378where
379    S: CanvasPersistenceStore,
380{
381    match effect {
382        CanvasToolEffect::ApplyTransaction(transaction) => {
383            apply_persistent_transaction(editor, store, cursor, transaction)?;
384        }
385        CanvasToolEffect::CommitGesture => {
386            apply_persistent_gesture_commit(editor, store, cursor)?;
387        }
388        effect => {
389            editor.apply_tool_effect(effect)?;
390        }
391    }
392
393    Ok(())
394}
395
396pub(crate) fn apply_persistent_tool_effects<S>(
397    editor: &mut CanvasEditor,
398    store: &mut S,
399    cursor: &mut CanvasPersistenceCursor,
400    effects: impl IntoIterator<Item = CanvasToolEffect>,
401) -> Result<(), CanvasPersistenceError<S::Error>>
402where
403    S: CanvasPersistenceStore,
404{
405    for effect in effects {
406        apply_persistent_tool_effect(editor, store, cursor, effect)?;
407    }
408
409    Ok(())
410}
411
412pub fn apply_persistent_tool_intent<S>(
413    editor: &mut CanvasEditor,
414    store: &mut S,
415    cursor: &mut CanvasPersistenceCursor,
416    intent: CanvasToolIntent,
417) -> Result<(), CanvasPersistenceError<S::Error>>
418where
419    S: CanvasPersistenceStore,
420{
421    match intent {
422        CanvasToolIntent::ApplyTransaction(transaction) => {
423            editor.apply_custom_tool_intent(CanvasToolIntent::ApplyTransaction(transaction))?;
424        }
425        CanvasToolIntent::CommitTransaction => {
426            apply_persistent_gesture_commit(editor, store, cursor)?;
427        }
428        intent => {
429            editor.apply_custom_tool_intent(intent)?;
430        }
431    }
432
433    Ok(())
434}
435
436pub fn apply_persistent_tool_intents<S>(
437    editor: &mut CanvasEditor,
438    store: &mut S,
439    cursor: &mut CanvasPersistenceCursor,
440    intents: impl IntoIterator<Item = CanvasToolIntent>,
441) -> Result<(), CanvasPersistenceError<S::Error>>
442where
443    S: CanvasPersistenceStore,
444{
445    for intent in intents {
446        apply_persistent_tool_intent(editor, store, cursor, intent)?;
447    }
448
449    Ok(())
450}
451
452pub fn handle_persistent_event<S>(
453    editor: &mut CanvasEditor,
454    store: &mut S,
455    cursor: &mut CanvasPersistenceCursor,
456    event: CanvasEvent,
457) -> Result<(), CanvasPersistenceError<S::Error>>
458where
459    S: CanvasPersistenceStore,
460{
461    let effects = editor.event_effects(event)?;
462    apply_persistent_tool_effects(editor, store, cursor, effects)
463}
464
465pub fn handle_persistent_event_with_custom_tool<S, T>(
466    editor: &mut CanvasEditor,
467    store: &mut S,
468    cursor: &mut CanvasPersistenceCursor,
469    event: CanvasEvent,
470    custom_tool: &mut T,
471) -> Result<(), CanvasPersistenceError<S::Error>>
472where
473    S: CanvasPersistenceStore,
474    T: CanvasToolReducer + ?Sized,
475{
476    if editor.tool().custom_id().is_some() {
477        let intents = custom_tool.handle_event(editor.tool_context(), event)?;
478        apply_persistent_tool_intents(editor, store, cursor, intents)
479    } else {
480        let effects = editor.event_effects(event)?;
481        apply_persistent_tool_effects(editor, store, cursor, effects)
482    }
483}
484
485pub fn handle_persistent_event_with_tool_registry<S>(
486    editor: &mut CanvasEditor,
487    store: &mut S,
488    cursor: &mut CanvasPersistenceCursor,
489    event: CanvasEvent,
490    registry: &mut CanvasToolRegistry,
491) -> Result<(), CanvasPersistentToolRegistryError<S::Error>>
492where
493    S: CanvasPersistenceStore,
494{
495    if let Some(tool_id) = editor.tool().custom_id().cloned() {
496        let reducer = registry
497            .reducer_mut(&tool_id)
498            .ok_or_else(|| CanvasPersistentToolRegistryError::MissingTool(tool_id.clone()))?;
499        let intents = reducer
500            .handle_event(editor.tool_context(), event)
501            .map_err(|error| {
502                CanvasPersistentToolRegistryError::Persistence(CanvasPersistenceError::Document(
503                    error,
504                ))
505            })?;
506        apply_persistent_tool_intents(editor, store, cursor, intents)
507            .map_err(CanvasPersistentToolRegistryError::from)?;
508    } else {
509        let effects = editor.event_effects(event).map_err(|error| {
510            CanvasPersistentToolRegistryError::Persistence(CanvasPersistenceError::Document(error))
511        })?;
512        apply_persistent_tool_effects(editor, store, cursor, effects)
513            .map_err(CanvasPersistentToolRegistryError::from)?;
514    }
515    Ok(())
516}
517
518pub fn save_canvas_checkpoint<S>(
519    editor: &CanvasEditor,
520    store: &mut S,
521    cursor: &CanvasPersistenceCursor,
522) -> Result<CanvasCheckpoint, CanvasPersistenceError<S::Error>>
523where
524    S: CanvasPersistenceStore,
525{
526    save_canvas_store_checkpoint(editor.store(), store, cursor)
527}
528
529pub fn save_canvas_store_checkpoint<S>(
530    canvas_store: &CanvasStore,
531    store: &mut S,
532    cursor: &CanvasPersistenceCursor,
533) -> Result<CanvasCheckpoint, CanvasPersistenceError<S::Error>>
534where
535    S: CanvasPersistenceStore,
536{
537    let checkpoint = CanvasCheckpoint::new(cursor.sequence(), canvas_store.document());
538    store
539        .save_checkpoint(checkpoint.clone())
540        .map_err(CanvasPersistenceError::Store)?;
541    store
542        .compact_log_entries(checkpoint.sequence)
543        .map_err(CanvasPersistenceError::Store)?;
544    Ok(checkpoint)
545}
546
547fn append_committed_log_entry<S>(
548    store: &mut S,
549    cursor: &CanvasPersistenceCursor,
550    committed: &CanvasCommittedMutation,
551) -> Result<(), CanvasPersistenceError<S::Error>>
552where
553    S: CanvasPersistenceStore,
554{
555    store
556        .append_log_entry(CanvasLogEntry::from_committed_mutation(
557            cursor.next_sequence(),
558            committed,
559        ))
560        .map_err(CanvasPersistenceError::Store)
561}
562
563fn append_then_apply_prepared_mutation<S>(
564    canvas_store: &mut CanvasStore,
565    store: &mut S,
566    cursor: &mut CanvasPersistenceCursor,
567    prepared: CanvasPreparedMutation,
568    apply: impl FnOnce(&mut CanvasStore, CanvasPreparedMutation) -> Option<CanvasStoreChange>,
569) -> Result<Option<CanvasStoreChange>, CanvasPersistenceError<S::Error>>
570where
571    S: CanvasPersistenceStore,
572{
573    let committed = prepared.committed().clone();
574    if committed.diff().is_empty() {
575        return Ok(apply(canvas_store, prepared));
576    }
577
578    append_committed_log_entry(store, cursor, &committed)?;
579    let change = apply(canvas_store, prepared);
580    debug_assert!(change.is_some());
581    cursor.advance();
582    Ok(change)
583}
584
585fn apply_persistent_gesture_commit<S>(
586    editor: &mut CanvasEditor,
587    store: &mut S,
588    cursor: &mut CanvasPersistenceCursor,
589) -> Result<(), CanvasPersistenceError<S::Error>>
590where
591    S: CanvasPersistenceStore,
592{
593    match editor.prepare_gesture_commit()? {
594        Some(prepared) => {
595            let committed = prepared.committed().clone();
596            if committed.diff().is_empty() {
597                editor.apply_prepared_gesture_store_change(prepared);
598                return Ok(());
599            }
600            append_committed_log_entry(store, cursor, &committed)?;
601            let change = editor.apply_prepared_gesture_store_change(prepared);
602            debug_assert!(change.is_some());
603            cursor.advance();
604        }
605        None => {
606            editor.apply_tool_effect(CanvasToolEffect::CommitGesture)?;
607        }
608    }
609
610    Ok(())
611}
612
613fn replay_checkpoint_and_log<E>(
614    checkpoint: Option<CanvasCheckpoint>,
615    log_entries: impl IntoIterator<Item = CanvasLogEntry>,
616) -> Result<CanvasDocument, CanvasPersistenceError<E>> {
617    let mut previous_sequence = checkpoint
618        .as_ref()
619        .map_or(0, |checkpoint| checkpoint.sequence);
620    let mut document = match checkpoint {
621        Some(checkpoint) => CanvasDocument::from_snapshot(checkpoint.snapshot)?,
622        None => CanvasDocument::default(),
623    };
624
625    for entry in log_entries {
626        if entry.sequence() <= previous_sequence {
627            return Err(CanvasPersistenceError::NonMonotonicLogSequence {
628                previous: previous_sequence,
629                found: entry.sequence(),
630            });
631        }
632
633        document.apply_transaction(entry.transaction().clone())?;
634        previous_sequence = entry.sequence();
635    }
636
637    Ok(document)
638}