feat(lapaloma-stream): osciloscopio CRT con RingBuffer en sweep mode

- lapaloma-stream: feature `gpui` (default). LapalomaStreamElement
  pinta un RingBuffer en modo sweep — dos polilíneas split-at-head
  (segmento [head..cap) viejo + [0..head) nuevo) para evitar la
  línea horizontal del wraparound. Pre-fill (count < cap) sólo
  pinta [0, head) para evitar el flat-line del 1.0.2 fix.
- y_range configurable, background opcional, padding.
- crates/apps/lapaloma-stream-demo: osciloscopio sintético con
  RingBuffer cap=512. Timer en cx.background_executor que hace
  push(synthesize(t)) + cx.notify() cada 16ms (60 Hz). Señal =
  suma de dos sinusoides desfasadas + jitter determinístico.
  Header muestra cap / head / filled% / t / revision en vivo.
- Workspace: registrada la app lapaloma-stream-demo.

Showcase del P2 zero-alloc: push(v) son 2 writes + 2 increments,
zero allocations per frame, ningún Vec se reasigna.

46 tests verdes (sin cambios; el stream se valida en runtime via demo).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-13 02:58:25 +00:00
parent 66d18ab47c
commit 4796f2652d
7 changed files with 381 additions and 11 deletions
@@ -1,19 +1,23 @@
//! `lapaloma-stream` — telemetría streaming tipo osciloscopio.
//!
//! Núcleo: `lapaloma_core::ring::RingBuffer` + render en dos
//! segmentos split-at-head (sweep) o con translate por frame
//! (scroll).
//! segmentos split-at-head (modo sweep). El emisor de samples vive
//! afuera del Element — típicamente en el `Render` host con un
//! timer `cx.background_executor().timer(...)` que llama a
//! `buffer.push(value)` y `cx.notify()` cada N ms.
//!
//! Módulos:
//! - **`envelope`** — downsample min/max por columna de pixel.
//! Incremental para sweep, single bounded pass para scroll
//! (ver sección 3.3 del ARCHITECTURE.md).
//! - **`element`** — `Element` GPUI con `Model<RingBuffer>`
//! observable. El push viene de otro thread; el Element se
//! redibuja sólo cuando `revision` cambió.
//! El Element clona el RingBuffer por frame (para cap = 512 son
//! 4 KB, irrelevante). Para capacidades grandes (100k+) la siguiente
//! optimización es pasar `Arc<RingBuffer>` con shared read y
//! mutación interna via `Mutex`/`AtomicU64` para el head.
#![forbid(unsafe_code)]
#![allow(dead_code)]
pub mod envelope {}
pub mod element {}
#[cfg(feature = "gpui")]
pub mod element;
#[cfg(feature = "gpui")]
pub use element::{lapaloma_stream, LapalomaStreamElement};