futuresdr_frontend/gui/
time.rs

1use futures::StreamExt;
2use gloo_render::request_animation_frame;
3use gloo_render::AnimationFrame;
4use rbl_circular_buffer::CircularBuffer;
5use reqwasm::websocket::futures::WebSocket;
6use reqwasm::websocket::Message;
7use wasm_bindgen::prelude::*;
8use wasm_bindgen::JsCast;
9use wasm_bindgen_futures::spawn_local;
10use web_sys::HtmlCanvasElement;
11use web_sys::WebGlRenderingContext as GL;
12use yew::prelude::*;
13
14pub enum Msg {
15    Data(Vec<u8>),
16    Status(String),
17    Render(f64),
18}
19
20#[wasm_bindgen]
21pub fn add_time(id: String, url: String, min: f32, max: f32) {
22    let document = gloo_utils::document();
23    let div = document.query_selector(&id).unwrap().unwrap();
24    yew::start_app_with_props_in_element::<Time>(div, Props { url, min, max });
25}
26
27#[derive(Clone, Properties, Default, PartialEq)]
28pub struct Props {
29    pub url: String,
30    pub min: f32,
31    pub max: f32,
32}
33
34pub struct Time {
35    canvas_ref: NodeRef,
36    _canvas: Option<HtmlCanvasElement>,
37    gl: Option<GL>,
38    _render_loop: Option<AnimationFrame>,
39    buff: CircularBuffer<f32>,
40}
41
42impl Component for Time {
43    type Message = Msg;
44    type Properties = Props;
45
46    fn create(ctx: &Context<Self>) -> Self {
47        let link = ctx.link().clone();
48        let url = ctx.props().url.clone();
49
50        spawn_local(async move {
51            let websocket = WebSocket::open(&url).unwrap();
52            let (_, mut rx) = websocket.split();
53
54            while let Some(msg) = rx.next().await {
55                match msg {
56                    Ok(Message::Text(s)) => link.send_message(Msg::Status(s)),
57                    Ok(Message::Bytes(v)) => link.send_message(Msg::Data(v)),
58                    _ => break,
59                }
60            }
61        });
62
63        Self {
64            canvas_ref: NodeRef::default(),
65            _canvas: None,
66            gl: None,
67            _render_loop: None,
68            buff: CircularBuffer::new(2048),
69        }
70    }
71
72    fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
73        let canvas = self.canvas_ref.cast::<HtmlCanvasElement>().unwrap();
74
75        let gl: GL = canvas
76            .get_context("webgl")
77            .unwrap()
78            .unwrap()
79            .dyn_into()
80            .unwrap();
81
82        self._canvas = Some(canvas);
83        self.gl = Some(gl);
84
85        if first_render {
86            let handle = {
87                let link = ctx.link().clone();
88                request_animation_frame(move |time| link.send_message(Msg::Render(time)))
89            };
90            self._render_loop = Some(handle);
91        }
92    }
93
94    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
95        match msg {
96            Msg::Render(timestamp) => {
97                self.render_gl(timestamp, ctx);
98            }
99            Msg::Data(b) => {
100                let v;
101                unsafe {
102                    let s = b.len() / 4;
103                    let p = b.as_ptr();
104                    v = std::slice::from_raw_parts(p as *const f32, s);
105                }
106                for i in v {
107                    self.buff.push(*i);
108                }
109            }
110            Msg::Status(s) => {
111                gloo_console::log!(format!("socket status {:?}", &s));
112            }
113        }
114        true
115    }
116
117    fn view(&self, _ctx: &Context<Self>) -> Html {
118        html! {
119            <canvas ref={self.canvas_ref.clone()} />
120        }
121    }
122}
123
124impl Time {
125    fn render_gl(&mut self, timestamp: f64, ctx: &Context<Self>) {
126        let gl = self.gl.as_ref().expect("GL Context not initialized!");
127
128        let l = self.buff.len();
129        let min = ctx.props().min;
130        let max = ctx.props().max;
131        let vertices: Vec<f32> = (self.buff)
132            .enumerate()
133            .flat_map(|(i, v)| {
134                vec![
135                    -1.0 + 2.0 * i as f32 / l as f32,
136                    (2.0 * (v - min) / (max - min)) - 1.0,
137                ]
138            })
139            .collect();
140
141        let vertex_buffer = gl.create_buffer().unwrap();
142        let verts = js_sys::Float32Array::from(vertices.as_slice());
143
144        gl.bind_buffer(GL::ARRAY_BUFFER, Some(&vertex_buffer));
145        gl.buffer_data_with_array_buffer_view(GL::ARRAY_BUFFER, &verts, GL::STATIC_DRAW);
146
147        let vert_code = r#"
148            attribute vec2 coordinates;
149            void main(void) {
150                gl_Position = vec4(coordinates, 0.0, 1.0);
151            }
152        "#;
153        let vert_shader = gl.create_shader(GL::VERTEX_SHADER).unwrap();
154        gl.shader_source(&vert_shader, vert_code);
155        gl.compile_shader(&vert_shader);
156
157        let frag_code = r#"
158            void main(void) {
159                gl_FragColor = vec4(1.0, 0.0, 0.0, 0.8);
160            }
161        "#;
162        let frag_shader = gl.create_shader(GL::FRAGMENT_SHADER).unwrap();
163        gl.shader_source(&frag_shader, frag_code);
164        gl.compile_shader(&frag_shader);
165
166        let shader_program = gl.create_program().unwrap();
167        gl.attach_shader(&shader_program, &vert_shader);
168        gl.attach_shader(&shader_program, &frag_shader);
169        gl.link_program(&shader_program);
170
171        gl.use_program(Some(&shader_program));
172
173        // Attach the position vector as an attribute for the GL context.
174        let position = gl.get_attrib_location(&shader_program, "coordinates") as u32;
175        gl.vertex_attrib_pointer_with_i32(position, 2, GL::FLOAT, false, 0, 0);
176        gl.enable_vertex_attrib_array(position);
177
178        // Attach the time as a uniform for the GL context.
179        let time = gl.get_uniform_location(&shader_program, "u_time");
180        gl.uniform1f(time.as_ref(), timestamp as f32);
181
182        gl.draw_arrays(GL::LINE_STRIP, 0, l as i32);
183
184        let handle = {
185            let link = ctx.link().clone();
186            request_animation_frame(move |time| link.send_message(Msg::Render(time)))
187        };
188        self._render_loop = Some(handle);
189    }
190}