tawasuyu/pineal
Backend-agnostic GPU visualization — a catalog of painters over one Canvas trait, in Rust.
⎇ df7773b4f2df1f1227adb0afd7afd60935e384e9
00_unanchay/pineal/pineal-core/src/buffer.rs
Historia
Quién tocó qué
crudo
chore: refresco desde el monorepo (pin tawasuyu.git v0.2.0)
1//! `DataBuffer` — buffer interleaved `[x0, y0, x1, y1, ...]` con
2//! revision counter para invalidación de cachés.
3//!
4//! Es la primitiva universal de Lapaloma: todo serie cartesiana,
5//! todo grafo de nodos, todo OHLC vive en uno de estos (o en una
6//! variante con stride distinto). El layout `f32` x `f32` es lo
7//! que el GPU consume sin transformación.
8
9/// Buffer de coordenadas planas `[x, y]` empacadas.
10///
11/// La longitud lógica (número de puntos) es `coords.len() / 2`.
12/// Mutar in-place (`set_xy`, `push`) bumpea `revision` — los
13/// painters comparan su `last_seen_revision` para decidir si
14/// rebuilear su caché.
15#[derive(Debug, Clone, Default)]
16pub struct DataBuffer {
17 coords: Vec<f32>,
18 revision: u64,
19}
20
21impl DataBuffer {
22 pub fn new() -> Self {
23 Self::default()
24 }
25
26 /// Reserva espacio para `n` puntos sin agregarlos. Úsalo al
27 /// montar el widget para que `push` no realloque después.
28 pub fn with_capacity(n: usize) -> Self {
29 Self {
30 coords: Vec::with_capacity(n * 2),
31 revision: 0,
32 }
33 }
34
35 /// Construye a partir de coords interleaved ya armadas.
36 /// Útil en tests y carga inicial.
37 pub fn from_interleaved(coords: Vec<f32>) -> Self {
38 assert!(coords.len() % 2 == 0, "interleaved coords deben ser pares");
39 Self {
40 coords,
41 revision: 0,
42 }
43 }
44
45 pub fn push(&mut self, x: f32, y: f32) {
46 self.coords.push(x);
47 self.coords.push(y);
48 self.revision = self.revision.wrapping_add(1);
49 }
50
51 /// Sobrescribe un punto existente. `i` es el índice de punto
52 /// (no de float), 0-based.
53 pub fn set_xy(&mut self, i: usize, x: f32, y: f32) {
54 self.coords[i * 2] = x;
55 self.coords[i * 2 + 1] = y;
56 self.revision = self.revision.wrapping_add(1);
57 }
58
59 /// Pisa el contenido completo con la nueva slice.
60 /// Útil para hidratar el buffer en un solo memcpy.
61 pub fn replace_from(&mut self, src: &[f32]) {
62 assert!(src.len() % 2 == 0);
63 self.coords.clear();
64 self.coords.extend_from_slice(src);
65 self.revision = self.revision.wrapping_add(1);
66 }
67
68 pub fn clear(&mut self) {
69 self.coords.clear();
70 self.revision = self.revision.wrapping_add(1);
71 }
72
73 pub fn len(&self) -> usize {
74 self.coords.len() / 2
75 }
76
77 pub fn is_empty(&self) -> bool {
78 self.coords.is_empty()
79 }
80
81 pub fn xy(&self, i: usize) -> (f32, f32) {
82 (self.coords[i * 2], self.coords[i * 2 + 1])
83 }
84
85 /// Slice plana lista para `drawRawPoints` / `wgpu::Buffer`
86 /// / `<polyline points>`. No realiza copia.
87 pub fn coords(&self) -> &[f32] {
88 &self.coords
89 }
90
91 pub fn revision(&self) -> u64 {
92 self.revision
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn push_y_len() {
102 let mut b = DataBuffer::with_capacity(4);
103 b.push(0.0, 1.0);
104 b.push(1.0, 2.0);
105 assert_eq!(b.len(), 2);
106 assert_eq!(b.xy(1), (1.0, 2.0));
107 }
108
109 #[test]
110 fn revision_bumps() {
111 let mut b = DataBuffer::new();
112 let r0 = b.revision();
113 b.push(0.0, 0.0);
114 let r1 = b.revision();
115 b.set_xy(0, 1.0, 1.0);
116 let r2 = b.revision();
117 assert_ne!(r0, r1);
118 assert_ne!(r1, r2);
119 }
120
121 #[test]
122 fn coords_slice_is_zero_copy() {
123 let raw = vec![0.0, 0.0, 1.0, 1.0, 2.0, 2.0];
124 let b = DataBuffer::from_interleaved(raw);
125 assert_eq!(b.coords(), &[0.0, 0.0, 1.0, 1.0, 2.0, 2.0]);
126 assert_eq!(b.len(), 3);
127 }
128}