gpui_liveplot/gpui_backend/
link.rs1use std::sync::{Arc, RwLock};
2
3use crate::view::{Range, Viewport};
4
5const LINK_EPSILON: f64 = 1e-9;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub struct LinkMemberId(u64);
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct PlotLinkOptions {
14 pub link_x: bool,
16 pub link_y: bool,
18 pub link_cursor: bool,
20 pub link_brush: bool,
22 pub link_reset: bool,
24}
25
26impl Default for PlotLinkOptions {
27 fn default() -> Self {
28 Self {
29 link_x: true,
30 link_y: false,
31 link_cursor: false,
32 link_brush: false,
33 link_reset: true,
34 }
35 }
36}
37
38#[derive(Debug, Clone, Default)]
40pub struct PlotLinkGroup {
41 inner: Arc<RwLock<LinkGroupState>>,
42}
43
44impl PlotLinkGroup {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub(crate) fn register_member(&self) -> LinkMemberId {
51 let mut state = self.inner.write().expect("link group lock");
52 state.next_member_id = state.next_member_id.wrapping_add(1);
53 LinkMemberId(state.next_member_id)
54 }
55
56 pub(crate) fn publish_manual_view(
57 &self,
58 source: LinkMemberId,
59 viewport: Viewport,
60 sync_x: bool,
61 sync_y: bool,
62 ) {
63 if !sync_x && !sync_y {
64 return;
65 }
66 let mut state = self.inner.write().expect("link group lock");
67 if let Some(current) = state.view_update
68 && let ViewSyncKind::Manual {
69 viewport: current_viewport,
70 sync_x: current_sync_x,
71 sync_y: current_sync_y,
72 } = current.kind
73 && current.source == source
74 && current_sync_x == sync_x
75 && current_sync_y == sync_y
76 && viewport_approx_eq(current_viewport, viewport)
77 {
78 return;
79 }
80 let seq = state.next_seq();
81 state.view_update = Some(ViewLinkUpdate {
82 seq,
83 source,
84 kind: ViewSyncKind::Manual {
85 viewport,
86 sync_x,
87 sync_y,
88 },
89 });
90 }
91
92 pub(crate) fn publish_reset(&self, source: LinkMemberId) {
93 let mut state = self.inner.write().expect("link group lock");
94 if let Some(current) = state.view_update
95 && matches!(current.kind, ViewSyncKind::Reset)
96 && current.source == source
97 {
98 return;
99 }
100 let seq = state.next_seq();
101 state.view_update = Some(ViewLinkUpdate {
102 seq,
103 source,
104 kind: ViewSyncKind::Reset,
105 });
106 }
107
108 pub(crate) fn publish_cursor_x(&self, source: LinkMemberId, x: Option<f64>) {
109 let mut state = self.inner.write().expect("link group lock");
110 if let Some(current) = state.cursor_update
111 && current.source == source
112 && option_f64_approx_eq(current.x, x)
113 {
114 return;
115 }
116 let seq = state.next_seq();
117 state.cursor_update = Some(CursorLinkUpdate { seq, source, x });
118 }
119
120 pub(crate) fn publish_brush_x(&self, source: LinkMemberId, x_range: Option<Range>) {
121 let mut state = self.inner.write().expect("link group lock");
122 if let Some(current) = state.brush_update
123 && current.source == source
124 && option_range_approx_eq(current.x_range, x_range)
125 {
126 return;
127 }
128 let seq = state.next_seq();
129 state.brush_update = Some(BrushLinkUpdate {
130 seq,
131 source,
132 x_range,
133 });
134 }
135
136 pub(crate) fn latest_view_update(&self) -> Option<ViewLinkUpdate> {
137 self.inner.read().expect("link group lock").view_update
138 }
139
140 pub(crate) fn latest_cursor_update(&self) -> Option<CursorLinkUpdate> {
141 self.inner.read().expect("link group lock").cursor_update
142 }
143
144 pub(crate) fn latest_brush_update(&self) -> Option<BrushLinkUpdate> {
145 self.inner.read().expect("link group lock").brush_update
146 }
147}
148
149#[derive(Debug, Default)]
150struct LinkGroupState {
151 next_member_id: u64,
152 next_seq: u64,
153 view_update: Option<ViewLinkUpdate>,
154 cursor_update: Option<CursorLinkUpdate>,
155 brush_update: Option<BrushLinkUpdate>,
156}
157
158impl LinkGroupState {
159 fn next_seq(&mut self) -> u64 {
160 self.next_seq = self.next_seq.wrapping_add(1);
161 self.next_seq
162 }
163}
164
165#[derive(Debug, Clone)]
166pub(crate) struct LinkBinding {
167 pub(crate) group: PlotLinkGroup,
168 pub(crate) member_id: LinkMemberId,
169 pub(crate) options: PlotLinkOptions,
170}
171
172#[derive(Debug, Clone, Copy)]
173pub(crate) struct ViewLinkUpdate {
174 pub(crate) seq: u64,
175 pub(crate) source: LinkMemberId,
176 pub(crate) kind: ViewSyncKind,
177}
178
179#[derive(Debug, Clone, Copy)]
180pub(crate) enum ViewSyncKind {
181 Manual {
182 viewport: Viewport,
183 sync_x: bool,
184 sync_y: bool,
185 },
186 Reset,
187}
188
189#[derive(Debug, Clone, Copy)]
190pub(crate) struct CursorLinkUpdate {
191 pub(crate) seq: u64,
192 pub(crate) source: LinkMemberId,
193 pub(crate) x: Option<f64>,
194}
195
196#[derive(Debug, Clone, Copy)]
197pub(crate) struct BrushLinkUpdate {
198 pub(crate) seq: u64,
199 pub(crate) source: LinkMemberId,
200 pub(crate) x_range: Option<Range>,
201}
202
203fn approx_eq(a: f64, b: f64) -> bool {
204 (a - b).abs() <= LINK_EPSILON
205}
206
207fn option_f64_approx_eq(a: Option<f64>, b: Option<f64>) -> bool {
208 match (a, b) {
209 (Some(a), Some(b)) => approx_eq(a, b),
210 (None, None) => true,
211 _ => false,
212 }
213}
214
215fn range_approx_eq(a: Range, b: Range) -> bool {
216 approx_eq(a.min, b.min) && approx_eq(a.max, b.max)
217}
218
219fn option_range_approx_eq(a: Option<Range>, b: Option<Range>) -> bool {
220 match (a, b) {
221 (Some(a), Some(b)) => range_approx_eq(a, b),
222 (None, None) => true,
223 _ => false,
224 }
225}
226
227fn viewport_approx_eq(a: Viewport, b: Viewport) -> bool {
228 range_approx_eq(a.x, b.x) && range_approx_eq(a.y, b.y)
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn manual_view_publish_deduplicates_same_payload() {
237 let group = PlotLinkGroup::new();
238 let member = group.register_member();
239 let viewport = Viewport::new(Range::new(0.0, 10.0), Range::new(-1.0, 1.0));
240
241 group.publish_manual_view(member, viewport, true, false);
242 let first = group.latest_view_update().expect("view update");
243 group.publish_manual_view(member, viewport, true, false);
244 let second = group.latest_view_update().expect("view update");
245
246 assert_eq!(first.seq, second.seq);
247 }
248
249 #[test]
250 fn reset_publish_replaces_previous_view_event() {
251 let group = PlotLinkGroup::new();
252 let member = group.register_member();
253 let viewport = Viewport::new(Range::new(0.0, 5.0), Range::new(0.0, 1.0));
254 group.publish_manual_view(member, viewport, true, false);
255 let first = group.latest_view_update().expect("view update").seq;
256
257 group.publish_reset(member);
258 let update = group.latest_view_update().expect("view update");
259 assert!(update.seq > first);
260 assert!(matches!(update.kind, ViewSyncKind::Reset));
261 }
262}