From 530a17f7af52417b9065466744a3153f473781a1 Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Fri, 2 Jun 2023 14:44:55 +0800 Subject: [PATCH] Populate dashboard lazily; add wasm wrapper code --- Cargo.lock | 181 +++++++++++++++- alvr/dashboard/Cargo.toml | 6 + .../src/dashboard/components/connections.rs | 194 ++++++++++-------- .../src/dashboard/components/installation.rs | 23 ++- .../dashboard/src/dashboard/components/mod.rs | 8 +- .../src/dashboard/components/settings.rs | 105 ++++++---- .../presets/builtin_schema.rs | 15 -- alvr/dashboard/src/dashboard/mod.rs | 39 ++-- alvr/dashboard/src/data_sources_wasm.rs | 63 ++++++ alvr/dashboard/src/main.rs | 27 ++- 10 files changed, 477 insertions(+), 184 deletions(-) create mode 100644 alvr/dashboard/src/data_sources_wasm.rs diff --git a/Cargo.lock b/Cargo.lock index ba33c44974..faf8a411af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -258,16 +258,22 @@ dependencies = [ "alvr_session", "bincode", "chrono", + "console_error_panic_hook", "eframe", "env_logger", + "ewebsock", + "futures", + "gloo-net", "ico", "instant", "serde", "serde_json", "settings-schema", "sysinfo", - "tungstenite", + "tungstenite 0.19.0", "ureq", + "wasm-bindgen-futures", + "wasm-logger", "winres", ] @@ -329,7 +335,7 @@ dependencies = [ "spin_sleep", "sysinfo", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.19.0", "tokio-util", "walkdir", ] @@ -621,6 +627,28 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + [[package]] name = "async-task" version = "4.4.0" @@ -1086,6 +1114,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -1589,6 +1627,25 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "ewebsock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "689197e24a57aee379b3bbef527e70607fc6d4b58fae4f1d98a2c6d91503e230" +dependencies = [ + "async-stream", + "futures", + "futures-util", + "js-sys", + "tokio", + "tokio-tungstenite 0.17.2", + "tracing", + "tungstenite 0.17.3", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "exec" version = "0.3.1" @@ -1977,6 +2034,39 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-net" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "glow" version = "0.12.2" @@ -3170,9 +3260,9 @@ checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" [[package]] name = "openssl" -version = "0.10.53" +version = "0.10.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df40a956736488b7b44fe79fe12d4f245bb5b3f5a1f6095e499760015be392" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -3343,6 +3433,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -3873,6 +3983,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha1" version = "0.10.5" @@ -4265,6 +4386,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.17.3", +] + [[package]] name = "tokio-tungstenite" version = "0.19.0" @@ -4274,7 +4407,7 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.19.0", ] [[package]] @@ -4384,6 +4517,25 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44dcf002ae3b32cd25400d6df128c5babec3927cd1eb7ce813cfff20eb6c3746" +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "tungstenite" version = "0.19.0" @@ -4630,6 +4782,17 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +[[package]] +name = "wasm-logger" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wayland-client" version = "0.29.5" @@ -4810,9 +4973,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41af2ea7d87bd41ad0a37146252d5f7c26490209f47f544b2ee3b3ff34c7732e" +checksum = "74851c2c8e5d97652e74c241d41b0656b31c924a45dcdecde83975717362cfa4" dependencies = [ "android_system_properties", "arrayvec", @@ -5237,9 +5400,9 @@ checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" [[package]] name = "xml-rs" -version = "0.8.13" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8f380ae16a37b30e6a2cf67040608071384b1450c189e61bea3ff57cde922d" +checksum = "52839dc911083a8ef63efa4d039d1f58b5e409f923e44c80828f206f66e5541c" [[package]] name = "xshell" diff --git a/alvr/dashboard/Cargo.toml b/alvr/dashboard/Cargo.toml index a86563b278..554a8890ea 100644 --- a/alvr/dashboard/Cargo.toml +++ b/alvr/dashboard/Cargo.toml @@ -33,11 +33,17 @@ tungstenite = "0.19" ureq = { version = "2", features = ["json"] } [target.'cfg(target_arch = "wasm32")'.dependencies] +console_error_panic_hook = "0.1" eframe = { version = "0.22", default-features = false, features = [ 'glow', # currently wgpu is broken on web "default_fonts", ] } +ewebsock = "0.2" +futures = "0.3" +gloo-net = "0.2" instant = { version = "0.1", features = ["wasm-bindgen"] } +wasm-bindgen-futures = "0.4" +wasm-logger = "0.2" [target.'cfg(windows)'.build-dependencies] winres = "0.1" diff --git a/alvr/dashboard/src/dashboard/components/connections.rs b/alvr/dashboard/src/dashboard/components/connections.rs index 0f5d88f7a7..8b93106f98 100644 --- a/alvr/dashboard/src/dashboard/components/connections.rs +++ b/alvr/dashboard/src/dashboard/components/connections.rs @@ -3,7 +3,7 @@ use crate::{ theme::{self, log_colors}, }; use alvr_packets::ClientListAction; -use alvr_session::SessionDesc; +use alvr_session::{ClientConnectionDesc, SessionDesc}; use eframe::{ egui::{Frame, Grid, Layout, RichText, TextEdit, Ui, Window}, emath::{Align, Align2}, @@ -18,23 +18,38 @@ struct EditPopupState { } pub struct ConnectionsTab { + new_clients: Option>, + trusted_clients: Option>, edit_popup_state: Option, } impl ConnectionsTab { pub fn new() -> Self { Self { + new_clients: None, + trusted_clients: None, edit_popup_state: None, } } - pub fn ui( - &mut self, - ui: &mut Ui, - session: &SessionDesc, - connected_to_server: bool, - ) -> Option { - let mut response = None; + pub fn update_client_list(&mut self, session: &SessionDesc) { + let (trusted_clients, untrusted_clients) = + session + .client_connections + .clone() + .into_iter() + .partition::, _>(|(_, data)| data.trusted); + + self.trusted_clients = Some(trusted_clients); + self.new_clients = Some(untrusted_clients); + } + + pub fn ui(&mut self, ui: &mut Ui, connected_to_server: bool) -> Vec { + let mut requests = vec![]; + + if self.new_clients.is_none() { + requests.push(ServerRequest::GetSession); + } if !connected_to_server { Frame::group(ui.style()) @@ -61,91 +76,90 @@ impl ConnectionsTab { }); } - // Get the different types of clients from the session - let (trusted_clients, untrusted_clients) = session - .client_connections - .iter() - .partition::, _>(|(_, data)| data.trusted); - ui.vertical_centered_justified(|ui| { - Frame::group(ui.style()) - .fill(theme::SECTION_BG) - .show(ui, |ui| { - ui.vertical_centered_justified(|ui| { - ui.add_space(5.0); - ui.heading("New clients"); - }); + if let Some(clients) = &self.new_clients { + Frame::group(ui.style()) + .fill(theme::SECTION_BG) + .show(ui, |ui| { + ui.vertical_centered_justified(|ui| { + ui.add_space(5.0); + ui.heading("New clients"); + }); - Grid::new(1).num_columns(2).show(ui, |ui| { - for (hostname, _) in untrusted_clients { - ui.horizontal(|ui| { - ui.add_space(10.0); - ui.label(hostname); - }); - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if ui.button("Trust").clicked() { - response = Some(ServerRequest::UpdateClientList { - hostname: hostname.clone(), - action: ClientListAction::Trust, - }); - }; - }); - ui.end_row(); - } - }) - }); + Grid::new(1).num_columns(2).show(ui, |ui| { + for (hostname, _) in clients { + ui.horizontal(|ui| { + ui.add_space(10.0); + ui.label(hostname); + }); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if ui.button("Trust").clicked() { + requests.push(ServerRequest::UpdateClientList { + hostname: hostname.clone(), + action: ClientListAction::Trust, + }); + }; + }); + ui.end_row(); + } + }) + }); + } ui.add_space(10.0); - Frame::group(ui.style()) - .fill(theme::SECTION_BG) - .show(ui, |ui| { - ui.vertical_centered_justified(|ui| { - ui.add_space(5.0); - ui.heading("Trusted clients"); - }); + if let Some(clients) = &self.trusted_clients { + Frame::group(ui.style()) + .fill(theme::SECTION_BG) + .show(ui, |ui| { + ui.vertical_centered_justified(|ui| { + ui.add_space(5.0); + ui.heading("Trusted clients"); + }); - Grid::new(2).num_columns(2).show(ui, |ui| { - for (hostname, data) in trusted_clients { - ui.horizontal(|ui| { - ui.add_space(10.0); - ui.label(format!( - "{hostname}: {} ({})", - data.current_ip.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), - data.display_name - )); - }); - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if ui.button("Remove").clicked() { - response = Some(ServerRequest::UpdateClientList { - hostname: hostname.clone(), - action: ClientListAction::RemoveEntry, - }); - } - if ui.button("Edit").clicked() { - self.edit_popup_state = Some(EditPopupState { - new_client: false, - hostname: hostname.to_owned(), - ips: data - .manual_ips - .iter() - .map(|addr| addr.to_string()) - .collect::>(), - }); - } + Grid::new(2).num_columns(2).show(ui, |ui| { + for (hostname, data) in clients { + ui.horizontal(|ui| { + ui.add_space(10.0); + ui.label(format!( + "{hostname}: {} ({})", + data.current_ip + .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + data.display_name + )); + }); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if ui.button("Remove").clicked() { + requests.push(ServerRequest::UpdateClientList { + hostname: hostname.clone(), + action: ClientListAction::RemoveEntry, + }); + } + if ui.button("Edit").clicked() { + self.edit_popup_state = Some(EditPopupState { + new_client: false, + hostname: hostname.to_owned(), + ips: data + .manual_ips + .iter() + .map(|addr| addr.to_string()) + .collect::>(), + }); + } + }); + ui.end_row(); + } + }); + + if ui.button("Add client manually").clicked() { + self.edit_popup_state = Some(EditPopupState { + hostname: "XXXX.client.alvr".into(), + new_client: true, + ips: Vec::new(), }); - ui.end_row(); } }); - - if ui.button("Add client manually").clicked() { - self.edit_popup_state = Some(EditPopupState { - hostname: "XXXX.client.alvr".into(), - new_client: true, - ips: Vec::new(), - }); - } - }); + } }); if let Some(mut state) = self.edit_popup_state.take() { @@ -165,7 +179,7 @@ impl ConnectionsTab { ui[1].text_edit_singleline(address); } if ui[1].button("Add new").clicked() { - state.ips.push("192.168.1.2".to_string()); + state.ips.push("192.168.X.X".to_string()); } }); ui.columns(2, |ui| { @@ -174,16 +188,16 @@ impl ConnectionsTab { state.ips.iter().filter_map(|s| s.parse().ok()).collect(); if state.new_client { - response = Some(ServerRequest::UpdateClientList { - hostname: state.hostname.clone(), + requests.push(ServerRequest::UpdateClientList { + hostname: state.hostname, action: ClientListAction::AddIfMissing { trusted: true, manual_ips, }, }); } else { - response = Some(ServerRequest::UpdateClientList { - hostname: state.hostname.clone(), + requests.push(ServerRequest::UpdateClientList { + hostname: state.hostname, action: ClientListAction::SetManualIps(manual_ips), }); } @@ -194,6 +208,6 @@ impl ConnectionsTab { }); } - response + requests } } diff --git a/alvr/dashboard/src/dashboard/components/installation.rs b/alvr/dashboard/src/dashboard/components/installation.rs index 39dc82cfa3..32ea8e4d27 100644 --- a/alvr/dashboard/src/dashboard/components/installation.rs +++ b/alvr/dashboard/src/dashboard/components/installation.rs @@ -4,7 +4,12 @@ use eframe::{ egui::{Frame, Grid, Layout, RichText, Ui}, emath::Align, }; -use std::path::PathBuf; +use std::{ + path::PathBuf, + time::{Duration, Instant}, +}; + +const DRIVER_UPDATE_INTERVAL: Duration = Duration::from_secs(1); pub enum InstallationTabRequest { OpenSetupWizard, @@ -13,11 +18,15 @@ pub enum InstallationTabRequest { pub struct InstallationTab { drivers: Vec, + last_update_instant: Instant, } impl InstallationTab { pub fn new() -> Self { - Self { drivers: vec![] } + Self { + drivers: vec![], + last_update_instant: Instant::now(), + } } pub fn update_drivers(&mut self, list: Vec) { @@ -26,6 +35,16 @@ impl InstallationTab { pub fn ui(&mut self, ui: &mut Ui) -> Vec { let mut requests = vec![]; + + let now = Instant::now(); + if now > self.last_update_instant + DRIVER_UPDATE_INTERVAL { + requests.push(InstallationTabRequest::ServerRequest( + ServerRequest::GetDriverList, + )); + + self.last_update_instant = now; + } + ui.vertical_centered_justified(|ui| { if ui.button("Run setup wizard").clicked() { requests.push(InstallationTabRequest::OpenSetupWizard); diff --git a/alvr/dashboard/src/dashboard/components/mod.rs b/alvr/dashboard/src/dashboard/components/mod.rs index 55e1de3dcc..d2a9c05878 100644 --- a/alvr/dashboard/src/dashboard/components/mod.rs +++ b/alvr/dashboard/src/dashboard/components/mod.rs @@ -1,7 +1,6 @@ mod about; mod connections; mod debug; -mod installation; mod logs; mod notifications; mod settings; @@ -9,13 +8,18 @@ mod settings_controls; mod setup_wizard; mod statistics; +#[cfg(not(target_arch = "wasm32"))] +mod installation; + pub use about::*; pub use connections::*; pub use debug::*; -pub use installation::*; pub use logs::*; pub use notifications::*; pub use settings::*; pub use settings_controls::*; pub use setup_wizard::*; pub use statistics::*; + +#[cfg(not(target_arch = "wasm32"))] +pub use installation::*; diff --git a/alvr/dashboard/src/dashboard/components/settings.rs b/alvr/dashboard/src/dashboard/components/settings.rs index bab23e5641..42344a51ec 100644 --- a/alvr/dashboard/src/dashboard/components/settings.rs +++ b/alvr/dashboard/src/dashboard/components/settings.rs @@ -14,71 +14,87 @@ pub struct SettingsTab { resolution_preset: PresetControl, framerate_preset: PresetControl, encoder_preset: PresetControl, - game_audio_preset: PresetControl, - microphone_preset: PresetControl, + game_audio_preset: Option, + microphone_preset: Option, eye_face_tracking_preset: PresetControl, advanced_grid_id: usize, - session_settings_json: json::Value, + session_settings_json: Option, root_control: SettingControl, } impl SettingsTab { pub fn new() -> Self { - let session_settings = alvr_session::session_settings_default(); - let nesting_info = NestingInfo { path: vec!["session_settings".into()], indentation_level: 0, }; - let schema = Settings::schema(session_settings.clone()); + let schema = Settings::schema(alvr_session::session_settings_default()); Self { presets_grid_id: get_id(), resolution_preset: PresetControl::new(builtin_schema::resolution_schema()), framerate_preset: PresetControl::new(builtin_schema::framerate_schema()), encoder_preset: PresetControl::new(builtin_schema::encoder_preset_schema()), - game_audio_preset: PresetControl::new(builtin_schema::null_preset_schema()), - microphone_preset: PresetControl::new(builtin_schema::null_preset_schema()), + game_audio_preset: None, + microphone_preset: None, eye_face_tracking_preset: PresetControl::new(builtin_schema::eye_face_tracking_schema()), advanced_grid_id: get_id(), - session_settings_json: json::to_value(session_settings).unwrap(), + session_settings_json: None, root_control: SettingControl::new(nesting_info, schema), } } pub fn update_session(&mut self, session_settings: &SessionSettings) { - self.session_settings_json = json::to_value(session_settings).unwrap(); + let settings_json = json::to_value(session_settings).unwrap(); self.resolution_preset - .update_session_settings(&self.session_settings_json); + .update_session_settings(&settings_json); self.framerate_preset - .update_session_settings(&self.session_settings_json); - self.encoder_preset - .update_session_settings(&self.session_settings_json); - self.game_audio_preset - .update_session_settings(&self.session_settings_json); - self.microphone_preset - .update_session_settings(&self.session_settings_json); + .update_session_settings(&settings_json); + self.encoder_preset.update_session_settings(&settings_json); + if let Some(preset) = self.game_audio_preset.as_mut() { + preset.update_session_settings(&settings_json) + } + if let Some(preset) = self.microphone_preset.as_mut() { + preset.update_session_settings(&settings_json) + } self.eye_face_tracking_preset - .update_session_settings(&self.session_settings_json); + .update_session_settings(&settings_json); + + self.session_settings_json = Some(settings_json); } pub fn update_audio_devices(&mut self, list: AudioDevicesList) { let mut all_devices = list.output.clone(); all_devices.extend(list.input); - self.game_audio_preset = PresetControl::new(builtin_schema::game_audio_schema(all_devices)); - self.game_audio_preset - .update_session_settings(&self.session_settings_json); + let settings_json = self + .session_settings_json + .clone() + .unwrap_or_else(|| json::to_value(alvr_session::session_settings_default()).unwrap()); - self.microphone_preset = PresetControl::new(builtin_schema::microphone_schema(list.output)); - self.microphone_preset - .update_session_settings(&self.session_settings_json); + let mut preset = PresetControl::new(builtin_schema::game_audio_schema(all_devices)); + preset.update_session_settings(&settings_json); + self.game_audio_preset = Some(preset); + + let mut preset = PresetControl::new(builtin_schema::microphone_schema(list.output)); + preset.update_session_settings(&settings_json); + self.microphone_preset = Some(preset); } - pub fn ui(&mut self, ui: &mut Ui) -> Option { + pub fn ui(&mut self, ui: &mut Ui) -> Vec { let mut requests = vec![]; + if self.session_settings_json.is_none() { + requests.push(ServerRequest::GetSession); + } + + if self.game_audio_preset.is_none() { + requests.push(ServerRequest::GetAudioDevices); + } + + let mut path_value_pairs = vec![]; + ui.heading("Presets"); ScrollArea::new([true, false]) .id_source(self.presets_grid_id) @@ -87,22 +103,26 @@ impl SettingsTab { .striped(true) .num_columns(2) .show(ui, |ui| { - requests.extend(self.resolution_preset.ui(ui)); + path_value_pairs.extend(self.resolution_preset.ui(ui)); ui.end_row(); - requests.extend(self.framerate_preset.ui(ui)); + path_value_pairs.extend(self.framerate_preset.ui(ui)); ui.end_row(); - requests.extend(self.encoder_preset.ui(ui)); + path_value_pairs.extend(self.encoder_preset.ui(ui)); ui.end_row(); - requests.extend(self.game_audio_preset.ui(ui)); - ui.end_row(); + if let Some(preset) = &mut self.game_audio_preset { + path_value_pairs.extend(preset.ui(ui)); + ui.end_row(); + } - requests.extend(self.microphone_preset.ui(ui)); - ui.end_row(); + if let Some(preset) = &mut self.microphone_preset { + path_value_pairs.extend(preset.ui(ui)); + ui.end_row(); + } - requests.extend(self.eye_face_tracking_preset.ui(ui)); + path_value_pairs.extend(self.eye_face_tracking_preset.ui(ui)); ui.end_row(); }) }); @@ -120,21 +140,20 @@ impl SettingsTab { .striped(true) .num_columns(2) .show(ui, |ui| { - if let Some(request) = - self.root_control - .ui(ui, &mut self.session_settings_json, false) - { - requests.push(request); + if let Some(json) = &mut self.session_settings_json { + if let Some(pair) = self.root_control.ui(ui, json, false) { + path_value_pairs.push(pair); + } } ui.end_row(); }) }); - if !requests.is_empty() { - Some(ServerRequest::SetValues(requests)) - } else { - None + if !path_value_pairs.is_empty() { + requests.push(ServerRequest::SetValues(path_value_pairs)); } + + requests } } diff --git a/alvr/dashboard/src/dashboard/components/settings_controls/presets/builtin_schema.rs b/alvr/dashboard/src/dashboard/components/settings_controls/presets/builtin_schema.rs index 97ea5f3edb..e512d96ba0 100644 --- a/alvr/dashboard/src/dashboard/components/settings_controls/presets/builtin_schema.rs +++ b/alvr/dashboard/src/dashboard/components/settings_controls/presets/builtin_schema.rs @@ -302,18 +302,3 @@ pub fn eye_face_tracking_schema() -> PresetSchemaNode { gui: ChoiceControlType::ButtonGroup, }) } - -pub fn null_preset_schema() -> PresetSchemaNode { - PresetSchemaNode::HigherOrderChoice(HigherOrderChoiceSchema { - name: "null".into(), - strings: HashMap::new(), - flags: HashSet::new(), - options: vec![HigherOrderChoiceOption { - display_name: "null".into(), - modifiers: vec![], - content: None, - }], - default_option_index: 0, - gui: ChoiceControlType::Dropdown, - }) -} diff --git a/alvr/dashboard/src/dashboard/mod.rs b/alvr/dashboard/src/dashboard/mod.rs index adb007a579..bb18c9bf47 100644 --- a/alvr/dashboard/src/dashboard/mod.rs +++ b/alvr/dashboard/src/dashboard/mod.rs @@ -2,8 +2,7 @@ mod basic_components; mod components; use self::components::{ - ConnectionsTab, InstallationTab, InstallationTabRequest, LogsTab, NotificationBar, SettingsTab, - SetupWizard, SetupWizardRequest, + ConnectionsTab, LogsTab, NotificationBar, SettingsTab, SetupWizard, SetupWizardRequest, }; use crate::{dashboard::components::StatisticsTab, theme, DataSources}; use alvr_common::parking_lot::{Condvar, Mutex}; @@ -51,6 +50,7 @@ enum Tab { Connections, Statistics, Settings, + #[cfg(not(target_arch = "wasm32"))] Installation, Logs, Debug, @@ -67,20 +67,17 @@ pub struct Dashboard { connections_tab: ConnectionsTab, statistics_tab: StatisticsTab, settings_tab: SettingsTab, - installation_tab: InstallationTab, + #[cfg(not(target_arch = "wasm32"))] + installation_tab: components::InstallationTab, logs_tab: LogsTab, notification_bar: NotificationBar, setup_wizard: SetupWizard, setup_wizard_open: bool, - session: SessionDesc, + session: Option, } impl Dashboard { pub fn new(creation_context: &eframe::CreationContext<'_>, data_sources: DataSources) -> Self { - data_sources.request(ServerRequest::GetSession); - data_sources.request(ServerRequest::GetAudioDevices); - data_sources.request(ServerRequest::GetDriverList); - theme::set_theme(&creation_context.egui_ctx); Self { @@ -93,6 +90,7 @@ impl Dashboard { (Tab::Connections, "🔌 Connections"), (Tab::Statistics, "📈 Statistics"), (Tab::Settings, "⚙ Settings"), + #[cfg(not(target_arch = "wasm32"))] (Tab::Installation, "💾 Installation"), (Tab::Logs, "📝 Logs"), (Tab::Debug, "🐞 Debug"), @@ -103,12 +101,13 @@ impl Dashboard { connections_tab: ConnectionsTab::new(), statistics_tab: StatisticsTab::new(), settings_tab: SettingsTab::new(), - installation_tab: InstallationTab::new(), + #[cfg(not(target_arch = "wasm32"))] + installation_tab: components::InstallationTab::new(), logs_tab: LogsTab::new(), notification_bar: NotificationBar::new(), setup_wizard: SetupWizard::new(), setup_wizard_open: false, - session: SessionDesc::default(), + session: None, } } @@ -161,6 +160,7 @@ impl eframe::App for Dashboard { EventType::Session(session) => { let settings = session.to_settings(); + self.connections_tab.update_client_list(&session); self.settings_tab.update_session(&session.session_settings); self.logs_tab.update_settings(&settings); self.notification_bar.update_settings(&settings); @@ -172,12 +172,13 @@ impl eframe::App for Dashboard { self.just_opened = false; } - self.session = *session; + self.session = Some(*session); } EventType::ServerRequestsSelfRestart => self.restart_steamvr(&mut requests), EventType::AudioDevices(list) => self.settings_tab.update_audio_devices(list), + #[cfg(not(target_arch = "wasm32"))] EventType::DriversList(list) => self.installation_tab.update_drivers(list), - EventType::Tracking(_) | EventType::Buttons(_) | EventType::Haptics(_) => (), + _ => (), } } @@ -284,12 +285,7 @@ impl eframe::App for Dashboard { ); ScrollArea::new([false, true]).show(ui, |ui| match self.selected_tab { Tab::Connections => { - if let Some(request) = - self.connections_tab - .ui(ui, &self.session, connected_to_server) - { - requests.push(request); - } + requests.extend(self.connections_tab.ui(ui, connected_to_server)); } Tab::Statistics => { if let Some(request) = self.statistics_tab.ui(ui) { @@ -299,13 +295,16 @@ impl eframe::App for Dashboard { Tab::Settings => { requests.extend(self.settings_tab.ui(ui)); } + #[cfg(not(target_arch = "wasm32"))] Tab::Installation => { for request in self.installation_tab.ui(ui) { match request { - InstallationTabRequest::OpenSetupWizard => { + components::InstallationTabRequest::OpenSetupWizard => { self.setup_wizard_open = true } - InstallationTabRequest::ServerRequest(request) => { + components::InstallationTabRequest::ServerRequest( + request, + ) => { requests.push(request); } } diff --git a/alvr/dashboard/src/data_sources_wasm.rs b/alvr/dashboard/src/data_sources_wasm.rs new file mode 100644 index 0000000000..9f0d0f8b79 --- /dev/null +++ b/alvr/dashboard/src/data_sources_wasm.rs @@ -0,0 +1,63 @@ +use alvr_events::Event; +use alvr_packets::ServerRequest; +use eframe::{egui, web_sys}; +use ewebsock::{WsEvent, WsMessage, WsReceiver}; +use gloo_net::http::Request; + +pub struct DataSources { + context: egui::Context, + ws_receiver: Option, +} + +impl DataSources { + pub fn new(context: egui::Context) -> Self { + Self { + context, + ws_receiver: None, + } + } + + pub fn request(&self, request: ServerRequest) { + let context = self.context.clone(); + wasm_bindgen_futures::spawn_local(async move { + Request::post("/api/dashboard-request") + .body(serde_json::to_string(&request).unwrap()) + .send() + .await + .ok(); + + context.request_repaint(); + }) + } + + pub fn poll_event(&mut self) -> Option { + if self.ws_receiver.is_none() { + let host = web_sys::window().unwrap().location().host().unwrap(); + let Ok((_, receiver)) = ewebsock::connect(format!("ws://{host}/api/events")) else { + return None; + }; + self.ws_receiver = Some(receiver); + } + + if let Some(event) = self.ws_receiver.as_ref().unwrap().try_recv() { + match event { + WsEvent::Message(WsMessage::Text(json_string)) => { + serde_json::from_str(&json_string).ok() + } + WsEvent::Error(_) | WsEvent::Closed => { + // recreate the ws connection next poll_event invocation + self.ws_receiver = None; + + None + } + _ => None, + } + } else { + None + } + } + + pub fn server_connected(&self) -> bool { + true + } +} diff --git a/alvr/dashboard/src/main.rs b/alvr/dashboard/src/main.rs index 653d66513d..ef3c21753e 100644 --- a/alvr/dashboard/src/main.rs +++ b/alvr/dashboard/src/main.rs @@ -1,16 +1,22 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +// hide console window on Windows in release +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod dashboard; -mod logging_backend; mod theme; #[cfg(not(target_arch = "wasm32"))] mod data_sources; +#[cfg(target_arch = "wasm32")] +mod data_sources_wasm; +#[cfg(not(target_arch = "wasm32"))] +mod logging_backend; #[cfg(not(target_arch = "wasm32"))] mod steamvr_launcher; #[cfg(not(target_arch = "wasm32"))] use data_sources::DataSources; +#[cfg(target_arch = "wasm32")] +use data_sources_wasm::DataSources; use dashboard::Dashboard; @@ -94,4 +100,19 @@ fn main() { } #[cfg(target_arch = "wasm32")] -fn main() {} +fn main() { + console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::default()); + + wasm_bindgen_futures::spawn_local(async { + eframe::WebRunner::new() + .start("dashboard_canvas", eframe::WebOptions::default(), { + Box::new(move |creation_context| { + let context = creation_context.egui_ctx.clone(); + Box::new(Dashboard::new(creation_context, DataSources::new(context))) + }) + }) + .await + .ok(); + }); +}