From 07867624be39c11fd6a08b5426bc96e30ebdfc87 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Mon, 7 Sep 2020 19:50:06 +0200 Subject: [PATCH] Fix event passing from agent to/from component --- Cargo.toml | 2 ++ src/agents/audio_agent.rs | 61 ++++++++++++++++++++++++++++++--------- src/canvas.rs | 51 +++++++++++++++++++++++++++----- src/controls.rs | 1 + 4 files changed, 95 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 02a5d5a..567e6f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] yew = "0.17" +#yew = { path = "/home/rob/dev/yew/yew" } yewtil = "0.3.1" wasm-bindgen = "0.2" gloo-events = "0.1.0" @@ -23,4 +24,5 @@ features = [ "console", "AudioContext", "AudioBuffer", + "HtmlCanvasElement", ] diff --git a/src/agents/audio_agent.rs b/src/agents/audio_agent.rs index 5f619ab..cdd0411 100644 --- a/src/agents/audio_agent.rs +++ b/src/agents/audio_agent.rs @@ -1,36 +1,54 @@ use js_sys::Uint8Array; +use std::collections::HashSet; +use std::rc::Rc; use wasm_bindgen::JsValue; use wasm_bindgen_futures::JsFuture; +use web_sys::console; use web_sys::{AudioContext, File}; use yew::services::reader::{FileChunk, ReaderTask}; -use yew::services::{ConsoleService, ReaderService}; +use yew::services::{ConsoleService as Console, ReaderService}; use yew::worker::*; use yewtil::future::LinkFuture; const FILE_LOAD_CHUNK_SIZE: usize = 4096; +pub type AudioData = Rc; + +#[derive(Debug)] pub struct AudioAgent { link: AgentLink, reader_service: ReaderService, reader_task: Option, audio_context: AudioContext, bytes_buffer: Vec, + subscribers: HashSet, } +#[derive(Debug)] pub enum Request { LoadSamplesFromFile(File), } +#[derive(Debug)] +pub enum Response { + SamplesLoaded(AudioData), +} + +#[derive(Debug)] pub enum Msg { FileProgress(Option), AudioDecoded(JsValue), AudioDecodingFailed(JsValue), } +// Convert the result of AudioContext.decode_audio_data(): impl From> for Msg { fn from(item: Result) -> Self { match item { - Ok(samples) => Msg::AudioDecoded(samples), + Ok(samples) => { + console::log_2(&"Got js value".into(), &samples); + Msg::AudioDecoded(samples) + } Err(e) => Msg::AudioDecodingFailed(e), } } @@ -40,7 +58,7 @@ impl Agent for AudioAgent { type Reach = Context; type Message = Msg; type Input = Request; - type Output = (); + type Output = Response; fn create(link: AgentLink) -> Self { // TODO: where should the AudioContext be initialized and stored? @@ -50,6 +68,7 @@ impl Agent for AudioAgent { reader_task: None, audio_context: AudioContext::new().unwrap(), bytes_buffer: vec![], + subscribers: HashSet::new(), } } @@ -60,17 +79,20 @@ impl Agent for AudioAgent { } Msg::FileProgress(Some(FileChunk::Finished)) => { if let Err(e) = self.handle_file_loaded() { - ConsoleService::error(&e); + Console::error(&e); } } Msg::AudioDecoded(samples) => { let audio_buffer = web_sys::AudioBuffer::from(samples); - let data = audio_buffer.get_channel_data(0).unwrap(); // TODO: error handling - let vec: Vec = data.to_vec(); - ConsoleService::log(&format!("Success decoding: {:?}", vec)); + let audio_data = Rc::new(audio_buffer); + + for subscriber in self.subscribers.iter() { + self.link + .respond(*subscriber, Response::SamplesLoaded(audio_data.clone())); + } } Msg::AudioDecodingFailed(err) => { - ConsoleService::error(&format!("Error decoding: {:?}", err)); + Console::error(&format!("Error decoding: {:?}", err)); } _ => (), }; @@ -81,28 +103,41 @@ impl Agent for AudioAgent { Request::LoadSamplesFromFile(file) => self.load_samples_from_file(file), }; } + + fn connected(&mut self, id: HandlerId) { + // if id.is_respondable() { + Console::log(&format!("Subscriber connected: {:?}", id)); + self.subscribers.insert(id); + // + } + + fn disconnected(&mut self, id: HandlerId) { + self.subscribers.remove(&id); + } } impl AudioAgent { - fn load_samples_from_file(&mut self, file: File) -> Result<(), String> { + fn load_samples_from_file(&mut self, file: File) { self.bytes_buffer = Vec::with_capacity(file.size() as usize); let cb = self.link.callback(Msg::FileProgress); + // TODO: error handling let task = self .reader_service .read_file_by_chunks(file, cb, FILE_LOAD_CHUNK_SIZE) - .map_err(|e| e.to_string())?; + .map_err(|e| e.to_string()) + .unwrap(); self.reader_task = Some(task); - - Ok(()) } fn handle_file_progress(&mut self, mut data: Vec, _progress: f32) { self.bytes_buffer.append(&mut data); } - fn handle_file_loaded(&self) -> Result<(), String> { + fn handle_file_loaded(&mut self) -> Result<(), String> { + self.reader_task = None; + let audio_data = Uint8Array::from(&self.bytes_buffer[..]); match self.audio_context.decode_audio_data(&audio_data.buffer()) { Ok(promise) => { diff --git a/src/canvas.rs b/src/canvas.rs index a0ad994..0b0b597 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -1,22 +1,46 @@ +use crate::agents::audio_agent::{self, AudioAgent, AudioData}; +use web_sys::HtmlCanvasElement; +use yew::agent::Bridged; use yew::prelude::*; +use yew::Bridge; -pub struct Canvas {} +pub struct Canvas { + _audio_agent: Box>, + canvas_node: NodeRef, + audio_data: Option, +} +#[derive(Debug)] pub enum Msg { Reset, - SamplesLoaded, + HandleAudioAgentResponse(audio_agent::Response), } impl Component for Canvas { type Message = Msg; type Properties = (); - fn create(_: Self::Properties, _: ComponentLink) -> Self { - Self {} + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Self { + _audio_agent: AudioAgent::bridge(link.callback(Msg::HandleAudioAgentResponse)), + canvas_node: NodeRef::default(), + audio_data: None, + } } - fn update(&mut self, _: Self::Message) -> ShouldRender { - true + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Reset => { + self.audio_data = None; + self.redraw_canvas(); + } + Msg::HandleAudioAgentResponse(audio_data) => match audio_data { + audio_agent::Response::SamplesLoaded(audio_data) => { + self.handle_samples_loaded(audio_data) + } + }, + }; + false } fn change(&mut self, _: Self::Properties) -> ShouldRender { @@ -24,6 +48,19 @@ impl Component for Canvas { } fn view(&self) -> Html { - html! { } + html! { } + } +} + +impl Canvas { + fn handle_samples_loaded(&mut self, audio_data: AudioData) { + self.audio_data = Some(audio_data); + self.redraw_canvas(); + } + + fn redraw_canvas(&mut self) { + let canvas_element = self.canvas_node.cast::().unwrap(); + let _context = canvas_element.get_context("2d").unwrap(); + // TODO: draw canvas... } } diff --git a/src/controls.rs b/src/controls.rs index 3c9fbc4..d493d70 100644 --- a/src/controls.rs +++ b/src/controls.rs @@ -1,5 +1,6 @@ use crate::agents::audio_agent::{self, AudioAgent}; use web_sys::HtmlInputElement; +use yew::agent::Dispatched; use yew::agent::Dispatcher; use yew::prelude::*; use yew::services::ConsoleService;