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}