gioser

tawasuyu/brahman

Rust 326TOML 123JSON 17Markdown 144606 Definitions

⎇ 2b2a92a72b8aa6d4e006189cbeda5a53e74a80c0 crates/apps/pineal-stream-demo/src/main.rs Rust · 125 lines · 3.9 KB History Who touched what raw

refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG 550c98f27 · sergio ·

1//! `pineal-stream-demo` — osciloscopio sintético.
2//!
3//! Ventana con un `LapalomaStreamElement` montado sobre un
4//! `RingBuffer` de 512 slots. Un timer en el background executor
5//! empuja un sample cada **16 ms** (≈ 60 Hz) y dispara
6//! `cx.notify()`. El sample es la suma de dos sinusoides desfasadas
7//! más un poquito de ruido determinístico.
8//!
9//! El efecto visual: la traza barre la ventana como en un
10//! osciloscopio CRT — split-at-head deja un "cursor" donde
11//! arranca la traza fresca, la traza vieja se mantiene a la
12//! derecha hasta que el cursor la sobrescriba.
13//!
14//! Showcase del **P2 zero-alloc en hot path**: el `push(v)` del
15//! RingBuffer son 2 escrituras + 2 increments. Cero allocations
16//! por frame, ningún `Vec` se reasigna.
17
18use std::time::Duration;
19
20use gpui::{div, prelude::*, px, Context, IntoElement, Render, Window};
21
22use pineal_core::ring::RingBuffer;
23use pineal_render::{Color, StrokeStyle};
24use pineal_stream::pineal_stream;
25use nahual_launcher::launch_app;
26use nahual_theme::Theme;
27
28const RING_CAPACITY: usize = 512;
29const SAMPLE_PERIOD: Duration = Duration::from_millis(16);
30
31fn main() {
32    launch_app(
33        "Lapaloma — stream (osciloscopio sintético 60 Hz)",
34        (900., 480.),
35        StreamDemo::new,
36    );
37}
38
39struct StreamDemo {
40    buffer: RingBuffer,
41    /// Tick count global. Sirve de fase para la señal sintética y
42    /// se muestra en el header para verificar que el timer corre.
43    t: u64,
44}
45
46impl StreamDemo {
47    fn new(cx: &mut Context<Self>) -> Self {
48        cx.spawn(async move |this, cx| {
49            let timer = cx.background_executor().clone();
50            loop {
51                timer.timer(SAMPLE_PERIOD).await;
52                let r = this.update(cx, |me, cx| {
53                    me.tick();
54                    cx.notify();
55                });
56                if r.is_err() {
57                    break;
58                }
59            }
60        })
61        .detach();
62
63        Self {
64            buffer: RingBuffer::new(RING_CAPACITY),
65            t: 0,
66        }
67    }
68
69    fn tick(&mut self) {
70        let v = synthesize(self.t);
71        self.buffer.push(v);
72        self.t = self.t.wrapping_add(1);
73    }
74}
75
76/// Señal sintética: suma de dos sinusoides + jitter determinístico.
77/// El rango efectivo queda en `[-1, 1]` aproximadamente.
78fn synthesize(t: u64) -> f32 {
79    let phase = t as f32;
80    let signal = (phase * 0.07).sin() * 0.75 + (phase * 0.19).sin() * 0.22;
81    let jitter = ((phase * 37.0).sin() * 1000.0).fract() * 0.04;
82    signal + jitter
83}
84
85impl Render for StreamDemo {
86    fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
87        let theme = Theme::global(cx).clone();
88
89        let plot_bg = Color::rgba(0.08, 0.10, 0.13, 1.0);
90        let stroke = StrokeStyle::new(1.8, Color::from_hex(0xa3be8c));
91
92        let stream = pineal_stream(self.buffer.clone(), stroke)
93            .background(plot_bg)
94            .y_range(-1.2, 1.2);
95
96        let fill_pct = (self.buffer.filled_len() * 100) / RING_CAPACITY;
97
98        div()
99            .size_full()
100            .bg(theme.bg_app.clone())
101            .p(px(16.))
102            .flex()
103            .flex_col()
104            .gap(px(10.))
105            .child(
106                div()
107                    .text_color(theme.fg_text)
108                    .text_size(px(18.))
109                    .child("Lapaloma — stream"),
110            )
111            .child(
112                div()
113                    .flex()
114                    .gap(px(16.))
115                    .text_size(px(11.))
116                    .text_color(theme.fg_muted)
117                    .child(format!("cap = {}", RING_CAPACITY))
118                    .child(format!("head = {}", self.buffer.head()))
119                    .child(format!("filled = {}%", fill_pct))
120                    .child(format!("t = {}", self.t))
121                    .child(format!("rev = {}", self.buffer.revision())),
122            )
123            .child(div().w_full().flex_grow().child(stream))
124    }
125}