|
| 1 | +//! Collaboration / replication hooks for the canvas graph. |
| 2 | +//! |
| 3 | +//! A [`SyncPlugin`] sits **beside** the local [`crate::Graph`]: the canvas still applies |
| 4 | +//! [`crate::GraphOp`] through commands and history, while the plugin mirrors those intents into a |
| 5 | +//! shared model (CRDT, document store, network sync, etc.) and pushes updates back through |
| 6 | +//! [`GraphChange`] so the UI stays consistent with peers and with undo/redo semantics. |
| 7 | +//! |
| 8 | +//! Implementations typically: |
| 9 | +//! - In [`SyncPlugin::setup`], subscribe to the shared model and forward diffs on |
| 10 | +//! [`UnboundedSender<GraphChange>`], setting [`crate::ChangeSource`] (`Local`, `Remote`, `Undo`, |
| 11 | +//! …) so the host can tell operator-driven edits from replay or remote merges. |
| 12 | +//! - In [`SyncPlugin::process_intent`], apply each [`GraphOp`] produced locally (after a command |
| 13 | +//! runs or history replays) into that model, using whatever metadata your stack needs to avoid |
| 14 | +//! mis-classifying those writes when your subscription fires again. |
| 15 | +//! - In [`SyncPlugin::undo`] / [`SyncPlugin::redo`], advance **your** backend undo manager if the |
| 16 | +//! sync layer owns a stack separate from the canvas history. |
| 17 | +//! |
| 18 | +//! The concrete backend (Yjs, operational transform, file append, etc.) is up to the plugin; this |
| 19 | +//! trait only defines the integration surface with the canvas. |
| 20 | +
|
1 | 21 | use futures::channel::mpsc::UnboundedSender; |
2 | 22 | use gpui::{AnyElement, Pixels, Point}; |
3 | 23 |
|
4 | 24 | use crate::{FlowEvent, GraphChange, GraphOp, RenderContext, Viewport}; |
5 | 25 |
|
| 26 | +/// Bridges local graph edits to a replicated or external graph model, and streams model changes |
| 27 | +/// back into the canvas. |
| 28 | +/// |
| 29 | +/// **Data flow (intended pattern)** |
| 30 | +/// 1. User action → canvas runs a command → [`GraphOp`]s are applied to the local graph. |
| 31 | +/// 2. The host forwards those ops to [`SyncPlugin::process_intent`] so the plugin updates its |
| 32 | +/// shared state. |
| 33 | +/// 3. Shared state emits updates (local echo, remote peer, or undo replay) → plugin sends |
| 34 | +/// [`GraphChange`] on the channel passed to [`SyncPlugin::setup`]. |
| 35 | +/// 4. Canvas applies those changes and refreshes; [`GraphChange::source`] distinguishes how each |
| 36 | +/// change should be treated (e.g. skip re-broadcasting remote edits). |
| 37 | +/// |
| 38 | +/// Keep [`process_intent`](SyncPlugin::process_intent) idempotent with respect to your own |
| 39 | +/// observers where possible: the same logical op may be reflected back through your subscription; |
| 40 | +/// tagging “local intent” vs “remote” vs “undo” origins is the usual way to stay consistent. |
6 | 41 | pub trait SyncPlugin { |
7 | 42 | fn name(&self) -> &'static str; |
8 | 43 |
|
| 44 | + /// One-time wiring: subscribe to the shared model, retain subscriptions for the plugin |
| 45 | + /// lifetime, and send [`GraphChange`] values on `change_sender` whenever the model moves. |
| 46 | + /// |
| 47 | + /// The host owns the receiver; do not block the UI thread on long-running I/O—spawn a task or |
| 48 | + /// use non-blocking channels as appropriate. |
9 | 49 | fn setup(&mut self, change_sender: UnboundedSender<GraphChange>); |
10 | 50 |
|
| 51 | + /// Apply a single local [`GraphOp`] (or a batch already decomposed by the host) into your |
| 52 | + /// backend. This is invoked for operator-driven edits after they hit the local graph, not as |
| 53 | + /// a replacement for the canvas command pipeline. |
11 | 54 | fn process_intent(&self, op: GraphOp); |
12 | 55 |
|
| 56 | + /// Step the sync-layer undo stack backward, if your backend maintains one in addition to (or |
| 57 | + /// instead of) mirroring canvas history. |
13 | 58 | fn undo(&mut self); |
| 59 | + /// Step the sync-layer undo stack forward. |
14 | 60 | fn redo(&mut self); |
15 | 61 |
|
| 62 | + /// Optional: handle canvas [`FlowEvent`]s for awareness, presence, or other non-[`GraphOp`] |
| 63 | + /// signals. Use [`SyncPluginContext`] for coordinate transforms when needed. |
16 | 64 | fn on_event(&mut self, _event: &FlowEvent, _ctx: &mut SyncPluginContext); |
17 | 65 |
|
| 66 | + /// Optional overlay (e.g. remote pointers) drawn with normal canvas [`RenderContext`]. |
18 | 67 | fn render(&mut self, _ctx: &mut RenderContext) -> Vec<AnyElement> { |
19 | 68 | vec![] |
20 | 69 | } |
|
0 commit comments