Skip to main content

open_gpui_canvas/
store.rs

1use std::sync::Arc;
2
3use crate::gesture::CanvasPreparedGestureCommit;
4use crate::mutation::CanvasPreparedMutation;
5use crate::{
6    CanvasCommittedMutation, CanvasDefaultEdgeRouter, CanvasDocument, CanvasDocumentDiff,
7    CanvasEdgeRouter, CanvasHistory, CanvasKindRegistry, CanvasRuntime, CanvasTransaction,
8    DocumentError,
9};
10
11#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
12pub enum CanvasStoreMutationSource {
13    #[default]
14    Local,
15    Gesture,
16}
17
18#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
19pub enum CanvasStoreHistoryEffect {
20    #[default]
21    None,
22    PushUndo,
23    Undo,
24    Redo,
25}
26
27#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
28pub struct CanvasStoreListenerId(u64);
29
30#[derive(Clone, Debug, PartialEq)]
31pub struct CanvasStoreChange {
32    source: CanvasStoreMutationSource,
33    history_effect: CanvasStoreHistoryEffect,
34    committed: CanvasCommittedMutation,
35    document: Arc<CanvasDocument>,
36    runtime: Arc<CanvasRuntime>,
37}
38
39impl CanvasStoreChange {
40    pub fn source(&self) -> CanvasStoreMutationSource {
41        self.source
42    }
43
44    pub fn history_effect(&self) -> CanvasStoreHistoryEffect {
45        self.history_effect
46    }
47
48    pub fn committed(&self) -> &CanvasCommittedMutation {
49        &self.committed
50    }
51
52    pub fn diff(&self) -> &CanvasDocumentDiff {
53        self.committed.diff()
54    }
55
56    pub fn document(&self) -> &CanvasDocument {
57        self.document.as_ref()
58    }
59
60    pub fn document_snapshot(&self) -> Arc<CanvasDocument> {
61        Arc::clone(&self.document)
62    }
63
64    pub fn runtime(&self) -> &CanvasRuntime {
65        self.runtime.as_ref()
66    }
67
68    pub fn runtime_snapshot(&self) -> Arc<CanvasRuntime> {
69        Arc::clone(&self.runtime)
70    }
71}
72
73struct CanvasStoreListener {
74    id: CanvasStoreListenerId,
75    callback: Box<dyn Fn(&CanvasStoreChange) + Send + Sync + 'static>,
76}
77
78pub struct CanvasStore {
79    document: Arc<CanvasDocument>,
80    runtime: Arc<CanvasRuntime>,
81    edge_router: Arc<dyn CanvasEdgeRouter + Send + Sync>,
82    kind_registry: Arc<CanvasKindRegistry>,
83    history: CanvasHistory,
84    listeners: Vec<CanvasStoreListener>,
85    next_listener_id: u64,
86}
87
88#[derive(Clone, Copy, Debug, Eq, PartialEq)]
89enum CanvasStoreHistoryDirection {
90    Undo,
91    Redo,
92}
93
94impl Default for CanvasStore {
95    fn default() -> Self {
96        Self::new(CanvasDocument::default())
97    }
98}
99
100impl CanvasStore {
101    pub fn new(document: CanvasDocument) -> Self {
102        Self::new_with_router(document, CanvasDefaultEdgeRouter)
103    }
104
105    pub fn new_with_router<R>(document: CanvasDocument, edge_router: R) -> Self
106    where
107        R: CanvasEdgeRouter + Send + Sync + 'static,
108    {
109        let edge_router = Arc::new(edge_router);
110        let kind_registry = Arc::new(CanvasKindRegistry::open());
111        let runtime = CanvasRuntime::rebuild_with_router_and_kind_registry(
112            &document,
113            edge_router.as_ref(),
114            kind_registry.as_ref(),
115        );
116        Self {
117            document: Arc::new(document),
118            runtime: Arc::new(runtime),
119            edge_router,
120            kind_registry,
121            history: CanvasHistory::default(),
122            listeners: Vec::new(),
123            next_listener_id: 0,
124        }
125    }
126
127    pub fn try_new_with_kind_registry(
128        document: CanvasDocument,
129        kind_registry: CanvasKindRegistry,
130    ) -> Result<Self, DocumentError> {
131        Self::try_new_with_router_and_kind_registry(
132            document,
133            CanvasDefaultEdgeRouter,
134            kind_registry,
135        )
136    }
137
138    pub fn try_new_with_router_and_kind_registry<R>(
139        document: CanvasDocument,
140        edge_router: R,
141        kind_registry: CanvasKindRegistry,
142    ) -> Result<Self, DocumentError>
143    where
144        R: CanvasEdgeRouter + Send + Sync + 'static,
145    {
146        let document = CanvasDocument::from_snapshot_with_kind_registry(
147            document.to_snapshot(),
148            &kind_registry,
149        )?;
150        let edge_router = Arc::new(edge_router);
151        let kind_registry = Arc::new(kind_registry);
152        let runtime = CanvasRuntime::rebuild_with_router_and_kind_registry(
153            &document,
154            edge_router.as_ref(),
155            kind_registry.as_ref(),
156        );
157        Ok(Self {
158            document: Arc::new(document),
159            runtime: Arc::new(runtime),
160            edge_router,
161            kind_registry,
162            history: CanvasHistory::default(),
163            listeners: Vec::new(),
164            next_listener_id: 0,
165        })
166    }
167
168    pub fn document(&self) -> &CanvasDocument {
169        self.document.as_ref()
170    }
171
172    pub(crate) fn document_snapshot(&self) -> Arc<CanvasDocument> {
173        Arc::clone(&self.document)
174    }
175
176    pub fn runtime(&self) -> &CanvasRuntime {
177        self.runtime.as_ref()
178    }
179
180    pub(crate) fn runtime_snapshot(&self) -> Arc<CanvasRuntime> {
181        Arc::clone(&self.runtime)
182    }
183
184    pub fn edge_router(&self) -> &(dyn CanvasEdgeRouter + Send + Sync) {
185        self.edge_router.as_ref()
186    }
187
188    pub fn kind_registry(&self) -> &CanvasKindRegistry {
189        self.kind_registry.as_ref()
190    }
191
192    pub(crate) fn kind_registry_snapshot(&self) -> Arc<CanvasKindRegistry> {
193        Arc::clone(&self.kind_registry)
194    }
195
196    pub fn history(&self) -> &CanvasHistory {
197        &self.history
198    }
199
200    #[cfg(test)]
201    pub(crate) fn history_mut_for_test(&mut self) -> &mut CanvasHistory {
202        &mut self.history
203    }
204
205    pub fn listen(
206        &mut self,
207        listener: impl Fn(&CanvasStoreChange) + Send + Sync + 'static,
208    ) -> CanvasStoreListenerId {
209        let id = CanvasStoreListenerId(self.next_listener_id);
210        self.next_listener_id += 1;
211        self.listeners.push(CanvasStoreListener {
212            id,
213            callback: Box::new(listener),
214        });
215        id
216    }
217
218    pub fn remove_listener(&mut self, id: CanvasStoreListenerId) -> bool {
219        let Some(index) = self.listeners.iter().position(|listener| listener.id == id) else {
220            return false;
221        };
222        self.listeners.remove(index);
223        true
224    }
225
226    pub fn apply_transaction(
227        &mut self,
228        transaction: CanvasTransaction,
229    ) -> Result<CanvasDocumentDiff, DocumentError> {
230        self.commit_transaction(transaction).map(|change| {
231            change.map_or_else(CanvasDocumentDiff::default, |change| change.diff().clone())
232        })
233    }
234
235    pub fn commit_transaction(
236        &mut self,
237        transaction: CanvasTransaction,
238    ) -> Result<Option<CanvasStoreChange>, DocumentError> {
239        if transaction.is_empty() {
240            return Ok(None);
241        }
242
243        let prepared = self.prepare_transaction(transaction)?;
244        Ok(self.apply_prepared_mutation(
245            prepared,
246            CanvasStoreMutationSource::Local,
247            CanvasStoreHistoryEffect::PushUndo,
248        ))
249    }
250
251    pub(crate) fn prepare_transaction(
252        &self,
253        transaction: CanvasTransaction,
254    ) -> Result<CanvasPreparedMutation, DocumentError> {
255        self.document
256            .prepare_transaction_with_kind_registry(transaction, self.kind_registry.as_ref())
257    }
258
259    pub(crate) fn apply_prepared_transaction(
260        &mut self,
261        prepared: CanvasPreparedMutation,
262    ) -> Option<CanvasStoreChange> {
263        self.apply_prepared_mutation(
264            prepared,
265            CanvasStoreMutationSource::Local,
266            CanvasStoreHistoryEffect::PushUndo,
267        )
268    }
269
270    pub(crate) fn prepare_undo(&self) -> Result<Option<CanvasPreparedMutation>, DocumentError> {
271        let Some(transaction) = self.history.next_undo_transaction().cloned() else {
272            return Ok(None);
273        };
274        self.prepare_transaction(transaction).map(Some)
275    }
276
277    pub(crate) fn apply_prepared_undo(
278        &mut self,
279        prepared: CanvasPreparedMutation,
280    ) -> Option<CanvasStoreChange> {
281        self.apply_prepared_history_mutation(
282            prepared,
283            CanvasStoreMutationSource::Local,
284            CanvasStoreHistoryEffect::Undo,
285            CanvasStoreHistoryDirection::Undo,
286        )
287    }
288
289    pub(crate) fn prepare_redo(&self) -> Result<Option<CanvasPreparedMutation>, DocumentError> {
290        let Some(transaction) = self.history.next_redo_transaction().cloned() else {
291            return Ok(None);
292        };
293        self.prepare_transaction(transaction).map(Some)
294    }
295
296    pub(crate) fn apply_prepared_redo(
297        &mut self,
298        prepared: CanvasPreparedMutation,
299    ) -> Option<CanvasStoreChange> {
300        self.apply_prepared_history_mutation(
301            prepared,
302            CanvasStoreMutationSource::Local,
303            CanvasStoreHistoryEffect::Redo,
304            CanvasStoreHistoryDirection::Redo,
305        )
306    }
307
308    pub fn undo(&mut self) -> Result<bool, DocumentError> {
309        let Some(prepared) = self.prepare_undo()? else {
310            return Ok(false);
311        };
312        Ok(self.apply_prepared_undo(prepared).is_some())
313    }
314
315    pub fn redo(&mut self) -> Result<bool, DocumentError> {
316        let Some(prepared) = self.prepare_redo()? else {
317            return Ok(false);
318        };
319        Ok(self.apply_prepared_redo(prepared).is_some())
320    }
321
322    pub(crate) fn apply_transient_transaction(
323        &mut self,
324        transaction: CanvasTransaction,
325    ) -> Result<CanvasDocumentDiff, DocumentError> {
326        if transaction.is_empty() {
327            return Ok(CanvasDocumentDiff::default());
328        }
329
330        let prepared = self.prepare_transaction(transaction)?;
331        let committed = prepared.apply_to(self.document_mut());
332        let diff = committed.diff().clone();
333        if diff.is_empty() {
334            return Ok(diff);
335        }
336
337        self.sync_runtime_committed(&committed);
338        Ok(diff)
339    }
340
341    pub(crate) fn apply_prepared_gesture_commit(
342        &mut self,
343        prepared: CanvasPreparedGestureCommit,
344    ) -> Option<CanvasStoreChange> {
345        let committed = prepared.committed().clone();
346        if committed.diff().is_empty() {
347            return None;
348        }
349
350        self.history.push_undo(committed.inverse().clone());
351        Some(self.finish_committed_mutation(
352            committed,
353            CanvasStoreMutationSource::Gesture,
354            CanvasStoreHistoryEffect::PushUndo,
355        ))
356    }
357
358    pub fn rebuild_runtime(&mut self) {
359        self.runtime = Arc::new(CanvasRuntime::rebuild_with_router_and_kind_registry(
360            self.document.as_ref(),
361            self.edge_router.as_ref(),
362            self.kind_registry.as_ref(),
363        ));
364    }
365
366    pub fn set_edge_router<R>(&mut self, edge_router: R)
367    where
368        R: CanvasEdgeRouter + Send + Sync + 'static,
369    {
370        self.edge_router = Arc::new(edge_router);
371        self.rebuild_runtime();
372    }
373
374    pub(crate) fn set_kind_registry(
375        &mut self,
376        kind_registry: CanvasKindRegistry,
377    ) -> Result<bool, DocumentError> {
378        let document = CanvasDocument::from_snapshot_with_kind_registry(
379            self.document.to_snapshot(),
380            &kind_registry,
381        )?;
382        let document_changed = document != *self.document;
383        self.document = Arc::new(document);
384        self.kind_registry = Arc::new(kind_registry);
385        if document_changed {
386            self.history.clear();
387        }
388        self.rebuild_runtime();
389        Ok(document_changed)
390    }
391
392    fn apply_prepared_mutation(
393        &mut self,
394        prepared: CanvasPreparedMutation,
395        source: CanvasStoreMutationSource,
396        history_effect: CanvasStoreHistoryEffect,
397    ) -> Option<CanvasStoreChange> {
398        let committed = prepared.apply_to(self.document_mut());
399        if committed.diff().is_empty() {
400            return None;
401        }
402
403        if history_effect == CanvasStoreHistoryEffect::PushUndo {
404            self.history.push_undo(committed.inverse().clone());
405        }
406
407        Some(self.finish_committed_mutation(committed, source, history_effect))
408    }
409
410    fn apply_prepared_history_mutation(
411        &mut self,
412        prepared: CanvasPreparedMutation,
413        source: CanvasStoreMutationSource,
414        history_effect: CanvasStoreHistoryEffect,
415        direction: CanvasStoreHistoryDirection,
416    ) -> Option<CanvasStoreChange> {
417        let expected_transaction = prepared.committed().transaction();
418        match direction {
419            CanvasStoreHistoryDirection::Undo => {
420                debug_assert_eq!(
421                    self.history.next_undo_transaction(),
422                    Some(expected_transaction)
423                );
424            }
425            CanvasStoreHistoryDirection::Redo => {
426                debug_assert_eq!(
427                    self.history.next_redo_transaction(),
428                    Some(expected_transaction)
429                );
430            }
431        }
432
433        let committed = prepared.apply_to(self.document_mut());
434        if committed.diff().is_empty() {
435            match direction {
436                CanvasStoreHistoryDirection::Undo => {
437                    let _ = self.history.pop_undo();
438                }
439                CanvasStoreHistoryDirection::Redo => {
440                    let _ = self.history.pop_redo();
441                }
442            }
443            return None;
444        }
445
446        match direction {
447            CanvasStoreHistoryDirection::Undo => {
448                let _ = self.history.pop_undo();
449                self.history.push_redo(committed.inverse().clone());
450            }
451            CanvasStoreHistoryDirection::Redo => {
452                let _ = self.history.pop_redo();
453                self.history.push_undo(committed.inverse().clone());
454            }
455        }
456
457        Some(self.finish_committed_mutation(committed, source, history_effect))
458    }
459
460    fn finish_committed_mutation(
461        &mut self,
462        committed: CanvasCommittedMutation,
463        source: CanvasStoreMutationSource,
464        history_effect: CanvasStoreHistoryEffect,
465    ) -> CanvasStoreChange {
466        self.sync_runtime_committed(&committed);
467        let change = CanvasStoreChange {
468            source,
469            history_effect,
470            committed,
471            document: Arc::clone(&self.document),
472            runtime: Arc::clone(&self.runtime),
473        };
474        self.emit_change(&change);
475        change
476    }
477
478    fn sync_runtime_committed(&mut self, committed: &CanvasCommittedMutation) {
479        let document = Arc::clone(&self.document);
480        let edge_router = Arc::clone(&self.edge_router);
481        let kind_registry = Arc::clone(&self.kind_registry);
482        self.runtime_mut()
483            .apply_committed_mutation_with_router_and_kind_registry(
484                document.as_ref(),
485                committed,
486                edge_router.as_ref(),
487                kind_registry.as_ref(),
488            );
489    }
490
491    fn emit_change(&self, change: &CanvasStoreChange) {
492        for listener in &self.listeners {
493            (listener.callback)(change);
494        }
495    }
496
497    fn document_mut(&mut self) -> &mut CanvasDocument {
498        Arc::make_mut(&mut self.document)
499    }
500
501    fn runtime_mut(&mut self) -> &mut CanvasRuntime {
502        Arc::make_mut(&mut self.runtime)
503    }
504}
505
506#[cfg(test)]
507mod tests {
508    use std::sync::{Arc, Mutex};
509
510    use open_gpui::{point, px, size};
511
512    use super::*;
513    use crate::{
514        CanvasNode, CanvasRecordId, CanvasRecordRelation, CanvasRelationChange, DocumentCommand,
515        NodeId, ShapeId,
516        test_support::{child_frame_fixture, document_fixture},
517    };
518
519    #[test]
520    fn store_rebuilds_runtime_from_initial_document() {
521        let document = document_fixture()
522            .node(CanvasNode::new(
523                "a",
524                point(px(0.0), px(0.0)),
525                size(px(10.0), px(10.0)),
526            ))
527            .build();
528
529        let store = CanvasStore::new(document);
530
531        assert!(store.document().contains_node(&NodeId::from("a")));
532        assert!(
533            store
534                .runtime()
535                .hit_test(point(px(5.0), px(5.0)), crate::HitOptions::default())
536                .any(|record| record.target == crate::HitTarget::Node(NodeId::from("a")))
537        );
538    }
539
540    #[test]
541    fn direct_transaction_updates_document_runtime_and_history() {
542        let mut store = CanvasStore::default();
543
544        let diff = store
545            .apply_transaction(CanvasTransaction::single(DocumentCommand::InsertNode(
546                CanvasNode::new("a", point(px(0.0), px(0.0)), size(px(10.0), px(10.0))),
547            )))
548            .unwrap();
549
550        assert!(!diff.is_empty());
551        assert!(store.document().contains_node(&NodeId::from("a")));
552        assert_eq!(store.history().undo_depth(), 1);
553        assert!(
554            store
555                .runtime()
556                .hit_test(point(px(5.0), px(5.0)), crate::HitOptions::default())
557                .any(|record| record.target == crate::HitTarget::Node(NodeId::from("a")))
558        );
559    }
560
561    #[test]
562    fn no_op_transaction_does_not_push_history_or_notify_listeners() {
563        let mut store = CanvasStore::default();
564        let changes = Arc::new(Mutex::new(Vec::new()));
565        let observed = Arc::clone(&changes);
566        store.listen(move |change| observed.lock().unwrap().push(change.clone()));
567
568        let diff = store
569            .apply_transaction(CanvasTransaction::single(DocumentCommand::RemoveNode(
570                NodeId::from("missing"),
571            )))
572            .unwrap_err();
573        assert_eq!(diff, DocumentError::MissingNode(NodeId::from("missing")));
574        assert_eq!(store.history().undo_depth(), 0);
575        assert!(changes.lock().unwrap().is_empty());
576
577        let diff = store
578            .apply_transaction(CanvasTransaction::default())
579            .unwrap();
580        assert!(diff.is_empty());
581        assert_eq!(store.history().undo_depth(), 0);
582        assert!(changes.lock().unwrap().is_empty());
583    }
584
585    #[test]
586    fn listeners_receive_post_commit_change_facts() {
587        let mut store = CanvasStore::default();
588        let changes = Arc::new(Mutex::new(Vec::new()));
589        let observed = Arc::clone(&changes);
590        store.listen(move |change| observed.lock().unwrap().push(change.clone()));
591
592        store
593            .apply_transaction(CanvasTransaction::single(DocumentCommand::InsertNode(
594                CanvasNode::new("a", point(px(0.0), px(0.0)), size(px(10.0), px(10.0))),
595            )))
596            .unwrap();
597
598        let changes = changes.lock().unwrap();
599        assert_eq!(changes.len(), 1);
600        let change = &changes[0];
601        assert_eq!(change.source(), CanvasStoreMutationSource::Local);
602        assert_eq!(change.history_effect(), CanvasStoreHistoryEffect::PushUndo);
603        assert!(change.document().contains_node(&NodeId::from("a")));
604        assert_eq!(change.committed().record_changes().len(), 1);
605    }
606
607    #[test]
608    fn listeners_receive_relation_only_change_facts() {
609        let child = CanvasRecordId::Node(NodeId::from("child"));
610        let frame = CanvasRecordId::Shape(ShapeId::from("frame"));
611        let document = child_frame_fixture().build();
612        let mut store = CanvasStore::new(document);
613        let changes = Arc::new(Mutex::new(Vec::new()));
614        let observed = Arc::clone(&changes);
615        store.listen(move |change| observed.lock().unwrap().push(change.clone()));
616
617        let diff = store
618            .apply_transaction(CanvasTransaction::single(
619                DocumentCommand::SetRecordParent {
620                    child: child.clone(),
621                    parent: frame.clone(),
622                },
623            ))
624            .unwrap();
625
626        assert!(diff.relations_changed);
627        let changes = changes.lock().unwrap();
628        assert_eq!(changes.len(), 1);
629        let change = &changes[0];
630        assert!(change.committed().record_changes().is_empty());
631        assert_eq!(
632            change.committed().relation_changes(),
633            &[CanvasRelationChange::Upsert(CanvasRecordRelation::Parent(
634                crate::relations::CanvasRecordParentRelation {
635                    child: child.clone(),
636                    parent: frame.clone(),
637                },
638            ))],
639        );
640        assert_eq!(
641            change.document().relations().parent_of(&child),
642            Some(&frame)
643        );
644    }
645
646    #[test]
647    fn store_changes_expose_relation_cleanup_for_deleted_records() {
648        let child = CanvasRecordId::Node(NodeId::from("child"));
649        let frame = CanvasRecordId::Shape(ShapeId::from("frame"));
650        let mut document = child_frame_fixture().build();
651        document
652            .apply_transaction(CanvasTransaction::single(
653                DocumentCommand::SetRecordParent {
654                    child: child.clone(),
655                    parent: frame.clone(),
656                },
657            ))
658            .unwrap();
659        let mut store = CanvasStore::new(document);
660        let changes = Arc::new(Mutex::new(Vec::new()));
661        let observed = Arc::clone(&changes);
662        store.listen(move |change| observed.lock().unwrap().push(change.clone()));
663
664        store
665            .apply_transaction(CanvasTransaction::single(DocumentCommand::RemoveNode(
666                NodeId::from("child"),
667            )))
668            .unwrap();
669
670        assert!(store.document().relations().is_empty());
671        let changes = changes.lock().unwrap();
672        assert_eq!(changes.len(), 1);
673        assert!(
674            changes[0]
675                .committed()
676                .relation_changes()
677                .iter()
678                .any(|change| matches!(
679                    change,
680                    CanvasRelationChange::Delete(CanvasRecordRelation::Parent(relation))
681                        if relation.child == child && relation.parent == frame
682                ))
683        );
684        drop(changes);
685
686        assert!(store.undo().unwrap());
687        assert_eq!(store.document().relations().parent_of(&child), Some(&frame));
688    }
689
690    #[test]
691    fn failed_transactions_do_not_notify_listeners() {
692        let mut store = CanvasStore::default();
693        let changes = Arc::new(Mutex::new(Vec::new()));
694        let observed = Arc::clone(&changes);
695        store.listen(move |change| observed.lock().unwrap().push(change.clone()));
696
697        let error = store
698            .apply_transaction(CanvasTransaction::single(
699                DocumentCommand::SetRecordParent {
700                    child: CanvasRecordId::Node(NodeId::from("missing")),
701                    parent: CanvasRecordId::Shape(ShapeId::from("frame")),
702                },
703            ))
704            .unwrap_err();
705
706        assert_eq!(
707            error,
708            DocumentError::MissingRelationRecord(CanvasRecordId::Node(NodeId::from("missing")))
709        );
710        assert!(changes.lock().unwrap().is_empty());
711        assert_eq!(store.history().undo_depth(), 0);
712    }
713
714    #[test]
715    fn listeners_fire_in_registration_order() {
716        let mut store = CanvasStore::default();
717        let calls = Arc::new(Mutex::new(Vec::new()));
718        let first = Arc::clone(&calls);
719        store.listen(move |_| first.lock().unwrap().push(1));
720        let second = Arc::clone(&calls);
721        store.listen(move |_| second.lock().unwrap().push(2));
722
723        store
724            .apply_transaction(CanvasTransaction::single(DocumentCommand::InsertNode(
725                CanvasNode::new("a", point(px(0.0), px(0.0)), size(px(10.0), px(10.0))),
726            )))
727            .unwrap();
728
729        assert_eq!(*calls.lock().unwrap(), vec![1, 2]);
730    }
731}