1#![warn(missing_docs)]
21
22use std::sync::Arc;
23use std::sync::Mutex;
24use std::sync::atomic::{AtomicU64, Ordering};
25
26use crate::scene::geometry::{GeometryId, next_geometry_id};
27
28#[derive(Clone, Copy, Debug, PartialEq)]
31pub struct Sample {
32 pub x: f64,
34 pub y: f64,
36}
37
38impl Sample {
39 pub fn new(x: f64, y: f64) -> Self {
41 Self { x, y }
42 }
43}
44
45impl From<(f64, f64)> for Sample {
46 fn from((x, y): (f64, f64)) -> Self {
47 Self { x, y }
48 }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Default)]
56pub struct SeriesBounds {
57 pub x: Option<(f64, f64)>,
59 pub y: Option<(f64, f64)>,
61}
62
63impl SeriesBounds {
64 pub fn of(samples: &[Sample]) -> Self {
66 let mut x: Option<(f64, f64)> = None;
67 let mut y: Option<(f64, f64)> = None;
68 for s in samples {
69 if s.x.is_finite() {
70 x = Some(match x {
71 Some((lo, hi)) => (lo.min(s.x), hi.max(s.x)),
72 None => (s.x, s.x),
73 });
74 }
75 if s.y.is_finite() {
76 y = Some(match y {
77 Some((lo, hi)) => (lo.min(s.y), hi.max(s.y)),
78 None => (s.y, s.y),
79 });
80 }
81 }
82 Self { x, y }
83 }
84
85 pub fn union(self, other: SeriesBounds) -> SeriesBounds {
87 SeriesBounds {
88 x: union_axis(self.x, other.x),
89 y: union_axis(self.y, other.y),
90 }
91 }
92}
93
94fn union_axis(a: Option<(f64, f64)>, b: Option<(f64, f64)>) -> Option<(f64, f64)> {
95 match (a, b) {
96 (Some((alo, ahi)), Some((blo, bhi))) => Some((alo.min(blo), ahi.max(bhi))),
97 (some, None) | (None, some) => some,
98 }
99}
100
101#[derive(Debug)]
103struct SeriesStore {
104 id: GeometryId,
105 samples: Mutex<Arc<Vec<Sample>>>,
106 bounds: Mutex<SeriesBounds>,
107 rev: AtomicU64,
108}
109
110#[derive(Clone, Debug)]
117pub struct SeriesHandle {
118 inner: Arc<SeriesStore>,
119}
120
121impl SeriesHandle {
122 pub fn new(samples: impl Into<Vec<Sample>>) -> Self {
124 let samples = samples.into();
125 let bounds = SeriesBounds::of(&samples);
126 Self {
127 inner: Arc::new(SeriesStore {
128 id: next_geometry_id(),
129 samples: Mutex::new(Arc::new(samples)),
130 bounds: Mutex::new(bounds),
131 rev: AtomicU64::new(0),
132 }),
133 }
134 }
135
136 pub fn empty() -> Self {
138 Self::new(Vec::new())
139 }
140
141 pub fn set(&self, samples: impl Into<Vec<Sample>>) {
145 let samples = samples.into();
146 *self.inner.bounds.lock().unwrap() = SeriesBounds::of(&samples);
147 *self.inner.samples.lock().unwrap() = Arc::new(samples);
148 self.inner.rev.fetch_add(1, Ordering::Relaxed);
149 }
150
151 pub fn append(&self, more: &[Sample]) {
154 if more.is_empty() {
155 return;
156 }
157 {
158 let mut guard = self.inner.samples.lock().unwrap();
159 let mut v = guard.as_ref().clone();
160 v.extend_from_slice(more);
161 *guard = Arc::new(v);
162 }
163 {
164 let mut b = self.inner.bounds.lock().unwrap();
165 *b = b.union(SeriesBounds::of(more));
166 }
167 self.inner.rev.fetch_add(1, Ordering::Relaxed);
168 }
169
170 pub fn id(&self) -> GeometryId {
173 self.inner.id
174 }
175
176 pub fn revision(&self) -> u64 {
178 self.inner.rev.load(Ordering::Relaxed)
179 }
180
181 pub fn bounds(&self) -> SeriesBounds {
183 *self.inner.bounds.lock().unwrap()
184 }
185
186 pub fn len(&self) -> usize {
188 self.inner.samples.lock().unwrap().len()
189 }
190
191 pub fn is_empty(&self) -> bool {
193 self.len() == 0
194 }
195
196 pub fn snapshot(&self) -> (Arc<Vec<Sample>>, u64) {
199 let rev = self.inner.rev.load(Ordering::Relaxed);
200 (Arc::clone(&self.inner.samples.lock().unwrap()), rev)
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn bounds_ignore_non_finite() {
210 let b = SeriesBounds::of(&[
211 Sample::new(1.0, 10.0),
212 Sample::new(f64::NAN, 5.0),
213 Sample::new(3.0, f64::INFINITY),
214 ]);
215 assert_eq!(b.x, Some((1.0, 3.0)));
216 assert_eq!(b.y, Some((5.0, 10.0)));
217 }
218
219 #[test]
220 fn empty_bounds_are_none() {
221 let b = SeriesBounds::of(&[]);
222 assert_eq!(b.x, None);
223 assert_eq!(b.y, None);
224 }
225
226 #[test]
227 fn set_bumps_revision_and_recomputes_bounds() {
228 let h = SeriesHandle::new(vec![Sample::new(0.0, 0.0)]);
229 assert_eq!(h.revision(), 0);
230 assert_eq!(h.bounds().x, Some((0.0, 0.0)));
231 h.set(vec![Sample::new(-1.0, 2.0), Sample::new(4.0, 9.0)]);
232 assert_eq!(h.revision(), 1);
233 assert_eq!(h.bounds().x, Some((-1.0, 4.0)));
234 assert_eq!(h.bounds().y, Some((2.0, 9.0)));
235 }
236
237 #[test]
238 fn append_grows_and_unions_bounds() {
239 let h = SeriesHandle::new(vec![Sample::new(0.0, 0.0)]);
240 h.append(&[Sample::new(5.0, -3.0)]);
241 assert_eq!(h.len(), 2);
242 assert_eq!(h.revision(), 1);
243 assert_eq!(h.bounds().x, Some((0.0, 5.0)));
244 assert_eq!(h.bounds().y, Some((-3.0, 0.0)));
245 h.append(&[]);
247 assert_eq!(h.revision(), 1);
248 }
249
250 #[test]
251 fn clones_share_storage_and_id() {
252 let a = SeriesHandle::new(vec![Sample::new(0.0, 0.0)]);
253 let b = a.clone();
254 assert_eq!(a.id(), b.id());
255 b.set(vec![Sample::new(1.0, 1.0), Sample::new(2.0, 2.0)]);
256 assert_eq!(a.len(), 2);
258 assert_eq!(a.revision(), 1);
259 }
260
261 #[test]
262 fn distinct_handles_have_distinct_ids() {
263 let a = SeriesHandle::new(Vec::new());
264 let b = SeriesHandle::new(Vec::new());
265 assert_ne!(a.id(), b.id());
266 }
267}