From 15ddce3475e0cd27cfc9f28962ed0d621a3d0e80 Mon Sep 17 00:00:00 2001 From: Casey Primozic Date: Mon, 5 Feb 2024 23:35:33 -0800 Subject: [PATCH] Foundation work for subgraph support * Switch stuff in the wasm engine to reference VCs by ID rather than index * Add some state for storing subgraph data * Each VC and foreign connectable gets tagged with a subgraph ID * Handle backwards compat for de/serialization --- .eslintignore | 1 + Justfile | 5 + engine/Cargo.lock | 49 +++- engine/engine/Cargo.toml | 6 +- engine/engine/src/js.rs | 6 +- engine/engine/src/lib.rs | 23 +- engine/engine/src/view_context/manager.rs | 272 +++++++++++++----- engine/note_container/Cargo.toml | 1 + engine/note_container/src/exports.rs | 5 +- engine/spectrum_viz/Cargo.toml | 1 + engine/spectrum_viz/src/line_viz/mod.rs | 3 +- package.json | 1 + .../VcHideStatusRegistry.ts | 5 +- src/ViewContextManager/ViewContextManager.tsx | 25 +- .../GenericPresetSaver.tsx | 4 +- src/graphEditor/GraphEditor.tsx | 4 +- .../ScaleAndShift/ResponsePlot.svelte | 4 +- src/patchNetwork/patchNetwork.ts | 6 + src/redux/modules/controlPanel.ts | 2 +- src/redux/modules/viewContextManager.ts | 44 ++- .../SignalAnalyzerGlobalControls.svelte | 4 +- src/vcInterop.ts | 50 +++- .../Oscilloscope/OscilloscopeControls.svelte | 4 +- tsconfig.json | 6 +- 24 files changed, 384 insertions(+), 147 deletions(-) diff --git a/.eslintignore b/.eslintignore index 06975cfb..81a4d97a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ src/engine.js src/engine_bg.d.ts *webpack* +src/vocalSynthesis diff --git a/Justfile b/Justfile index 186b2c9d..3cf24b8f 100644 --- a/Justfile +++ b/Justfile @@ -198,6 +198,11 @@ debug-engine: cd .. && wasm-bindgen ./target/wasm32-unknown-unknown/debug/engine.wasm --browser --remove-producers-section --out-dir ./build && \ cp ./build/* ../src +build-engine: + cd ./engine/engine && cargo build --release --target wasm32-unknown-unknown && \ + cd .. && wasm-bindgen ./target/wasm32-unknown-unknown/release/engine.wasm --browser --remove-producers-section --out-dir ./build && \ + cp ./build/* ../src + build-wavetable: cd ./engine/wavetable && cargo build --release --target wasm32-unknown-unknown && \ cp ../target/wasm32-unknown-unknown/release/wavetable.wasm ../../public diff --git a/engine/Cargo.lock b/engine/Cargo.lock index 4329c415..2bc6f25d 100644 --- a/engine/Cargo.lock +++ b/engine/Cargo.lock @@ -210,10 +210,12 @@ name = "engine" version = "0.1.0" dependencies = [ "common", + "fxhash", "log", - "miniserde", "rand", "rand_pcg 0.2.1", + "serde", + "serde_json", "uuid", "wasm-bindgen", "wbg_logging", @@ -345,6 +347,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -581,6 +592,7 @@ version = "0.1.0" dependencies = [ "common", "float-ord", + "fxhash", "js-sys", "log", "wasm-bindgen", @@ -955,6 +967,37 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2 1.0.69", + "quote 1.0.33", + "syn 2.0.38", +] + +[[package]] +name = "serde_json" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sidechain" version = "0.1.0" @@ -991,6 +1034,7 @@ name = "spectrum_viz" version = "0.1.0" dependencies = [ "canvas_utils", + "common", "lazy_static", "log", "miniserde", @@ -1098,6 +1142,9 @@ name = "uuid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +dependencies = [ + "serde", +] [[package]] name = "version_check" diff --git a/engine/engine/Cargo.toml b/engine/engine/Cargo.toml index f2296dd4..2b384398 100644 --- a/engine/engine/Cargo.toml +++ b/engine/engine/Cargo.toml @@ -11,10 +11,12 @@ crate-type = ["cdylib", "rlib"] wasm-bindgen = { version = "=0.2.82" } rand = "0.7" rand_pcg = "0.2.1" -miniserde = "0.1.34" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" # Disable logging staticly in release, making all log calls into no-ops log = { version = "0.4", features = ["release_max_level_warn"] } -uuid = { version = "1.2" } +uuid = { version = "1.2", features = ["serde"] } +fxhash = "0.2" common = { path = "../common" } wbg_logging = { path = "../wbg_logging" } diff --git a/engine/engine/src/js.rs b/engine/engine/src/js.rs index 852e0255..fd48334f 100644 --- a/engine/engine/src/js.rs +++ b/engine/engine/src/js.rs @@ -5,14 +5,16 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen(raw_module = "./vcInterop")] extern "C" { pub fn init_view_contexts( - active_context_ix: usize, + active_context_id: &str, view_context_definitions: &str, connections_json: &str, foreign_connectables_json: &str, + active_subgraph_id: &str, + subgraphs_by_id_json: &str, ); pub fn add_view_context(id: &str, name: &str); pub fn delete_view_context(id: &str); - pub fn set_active_vc_ix(new_ix: usize); + pub fn set_active_vc_id(new_id: &str); pub fn list_foreign_node_used_samples(id: &str) -> Vec; /// Returns the ID of the active view context to display after initializing pub fn initialize_default_vcm_state(); diff --git a/engine/engine/src/lib.rs b/engine/engine/src/lib.rs index a596fede..48a2246f 100644 --- a/engine/engine/src/lib.rs +++ b/engine/engine/src/lib.rs @@ -7,7 +7,6 @@ extern crate log; use std::{ptr, str::FromStr}; -use miniserde::json; use prelude::js::js_random; use uuid::Uuid; use wasm_bindgen::prelude::*; @@ -57,7 +56,8 @@ pub fn init() { unsafe { VIEW_CONTEXT_MANAGER = Box::into_raw(vcm) }; } -/// Creates a new view context from the provided name and sets it as the main view context. +/// Creates a new view context from the provided name in the active subgraph and sets it as the main +/// view context for that subgraph. #[wasm_bindgen] pub fn create_view_context(vc_name: String, display_name: String) { let uuid = uuid_v4(); @@ -66,7 +66,7 @@ pub fn create_view_context(vc_name: String, display_name: String) { view_context.init(); view_context.hide(); let vcm = get_vcm(); - vcm.add_view_context(uuid, vc_name, view_context); + vcm.add_view_context(uuid, vc_name, view_context, vcm.active_subgraph_id); set_vc_title(uuid.to_string(), display_name) } @@ -90,7 +90,14 @@ pub fn delete_vc_by_id(id: &str) { pub fn switch_view_context(uuid_str: &str) { let uuid = Uuid::from_str(uuid_str).expect("Invalid UUID string passed to `switch_view_context`!"); - get_vcm().set_active_view_by_id(uuid); + get_vcm().set_active_view(uuid); +} + +#[wasm_bindgen] +pub fn add_subgraph() { + let vcm = get_vcm(); + let new_subgraph_id = vcm.add_subgraph(); + vcm.set_active_subgraph(new_subgraph_id); } #[wasm_bindgen] @@ -98,9 +105,9 @@ pub fn reset_vcm() { info!("Resetting VCM..."); get_vcm().reset(); info!( - "Finished reset; current context count: {}, active_ix: {}", + "Finished reset; current context count: {}, active_id: {}", get_vcm().contexts.len(), - get_vcm().active_context_ix + get_vcm().active_context_id ); } @@ -133,7 +140,7 @@ pub fn get_vc_connectables(vc_id: &str) -> JsValue { #[wasm_bindgen] pub fn set_connections(connections_json: &str) { let connections: Vec<(ConnectionDescriptor, ConnectionDescriptor)> = - match json::from_str(connections_json) { + match serde_json::from_str(connections_json) { Ok(conns) => conns, Err(err) => { error!("Failed to deserialize provided connections JSON: {:?}", err); @@ -147,7 +154,7 @@ pub fn set_connections(connections_json: &str) { #[wasm_bindgen] pub fn set_foreign_connectables(foreign_connectables_json: &str) { let foreign_connectables: Vec = - match json::from_str(foreign_connectables_json) { + match serde_json::from_str(foreign_connectables_json) { Ok(conns) => conns, Err(err) => { error!( diff --git a/engine/engine/src/view_context/manager.rs b/engine/engine/src/view_context/manager.rs index 4c1452c6..d81aa3c9 100644 --- a/engine/engine/src/view_context/manager.rs +++ b/engine/engine/src/view_context/manager.rs @@ -1,6 +1,7 @@ use std::str::FromStr; -use miniserde::{json, Deserialize, Serialize}; +use fxhash::FxHashMap; +use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ @@ -31,6 +32,8 @@ pub struct MinimalViewContextDefinition { pub name: String, pub uuid: String, pub title: Option, + #[serde(default = "Uuid::nil", rename = "subgraphId")] + pub subgraph_id: Uuid, } pub struct ViewContextEntry { @@ -56,24 +59,39 @@ pub struct ForeignConnectable { #[serde(rename = "type")] pub _type: String, pub id: String, + #[serde(default = "Uuid::nil", rename = "subgraphId")] + pub subgraph_id: Uuid, #[serde(rename = "serializedState")] - pub serialized_state: Option, + pub serialized_state: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct SubgraphDescriptor { + pub id: Uuid, + pub name: String, + #[serde(rename = "activeVcId")] + pub active_vc_id: Uuid, } pub struct ViewContextManager { - pub active_context_ix: usize, + pub active_context_id: Uuid, pub contexts: Vec, pub connections: Vec<(ConnectionDescriptor, ConnectionDescriptor)>, pub foreign_connectables: Vec, + pub subgraphs_by_id: FxHashMap, + /// Nil UUID corresponds to the root graph + pub active_subgraph_id: Uuid, } impl Default for ViewContextManager { fn default() -> Self { ViewContextManager { - active_context_ix: 0, + active_context_id: Uuid::nil(), contexts: Vec::new(), connections: Vec::new(), foreign_connectables: Vec::new(), + subgraphs_by_id: Default::default(), + active_subgraph_id: Uuid::nil(), } } } @@ -83,14 +101,6 @@ pub struct ViewContextDefinition { pub minimal_def: MinimalViewContextDefinition, } -impl<'a> Into for &'a mut ViewContextEntry { - fn into(self) -> ViewContextDefinition { - ViewContextDefinition { - minimal_def: self.definition.clone(), - } - } -} - /// Represents a connection between two `ViewContext`s. It holds the ID of the src and dst VC along /// with the name of the input and output that are connected. #[derive(Clone, Serialize, Deserialize)] @@ -101,15 +111,23 @@ pub struct ConnectionDescriptor { } /// Represents the state of the application in a form that can be serialized and deserialized into -/// the browser's `localstorage` to refresh the state from scratch when the application reloads. +/// the browser's `localStorage` to refresh the state from scratch when the application reloads. #[derive(Serialize, Deserialize)] struct ViewContextManagerState { /// This contains the IDs of all managed VCs. The actual `ViewContextDefinition`s for each of /// them are found in separate `localStorage` entries. pub view_context_ids: Vec, - pub active_view_ix: usize, + #[serde(rename = "active_view_ix")] + pub deprecated_active_view_ix: usize, + #[serde(default = "Uuid::nil")] + pub active_view_id: Uuid, pub patch_network_connections: Vec<(ConnectionDescriptor, ConnectionDescriptor)>, pub foreign_connectables: Vec, + #[serde(default)] + pub subgraphs_by_id: FxHashMap, + /// Nil UUID corresponds to the root graph + #[serde(default = "Uuid::nil")] + pub active_subgraph_id: Uuid, } fn get_vc_key(uuid: Uuid) -> String { format!("vc_{}", uuid) } @@ -137,12 +155,14 @@ impl ViewContextManager { uuid: Uuid, name: String, view_context: Box, + subgraph_id: Uuid, ) -> usize { let created_ix = self.add_view_context_inner( MinimalViewContextDefinition { uuid: uuid.to_string(), name: name.clone(), title: None, + subgraph_id, }, view_context, ); @@ -163,22 +183,6 @@ impl ViewContextManager { self.get_vc_position(uuid).map(move |ix| &self.contexts[ix]) } - /// Given the UUID of a managed `ViewContext`, switches it to be the active view. - pub fn set_active_view_by_id(&mut self, id: Uuid) { - let ix = match self.get_vc_position(id) { - Some(ix) => ix, - None => { - error!( - "Tried to switch the active VC to one with ID {} but it wasn't found.", - id - ); - return; - }, - }; - - self.set_active_view(ix); - } - fn init_from_state_snapshot(&mut self, vcm_state: ViewContextManagerState) { for vc_id in vcm_state.view_context_ids { let definition_str = match js::get_localstorage_key(&format!("vc_{}", vc_id)) { @@ -191,7 +195,7 @@ impl ViewContextManager { continue; }, }; - let definition: ViewContextDefinition = match json::from_str(&definition_str) { + let definition: ViewContextDefinition = match serde_json::from_str(&definition_str) { Ok(definition) => definition, Err(err) => { error!("Error deserializing `ViewContextDefinition`: {:?}", err); @@ -210,14 +214,33 @@ impl ViewContextManager { self.add_view_context_inner(definition.minimal_def, view_context); } - self.active_context_ix = vcm_state.active_view_ix; + if !vcm_state.active_view_id.is_nil() { + self.active_context_id = vcm_state.active_view_id; + } else { + self.active_context_id = self + .contexts + .get(vcm_state.deprecated_active_view_ix) + .map(|vc| vc.id) + .unwrap_or_else(Uuid::nil); + } self.connections = vcm_state.patch_network_connections; self.foreign_connectables = vcm_state.foreign_connectables; + + self.subgraphs_by_id = vcm_state.subgraphs_by_id; + if self.subgraphs_by_id.is_empty() { + self + .subgraphs_by_id + .insert(Uuid::nil(), SubgraphDescriptor { + id: Uuid::nil(), + name: "Root".to_string(), + active_vc_id: self.active_context_id, + }); + } } fn load_vcm_state() -> Option { let vcm_state_str_opt = js::get_localstorage_key(VCM_STATE_KEY); - vcm_state_str_opt.and_then(|vcm_state_str| match json::from_str(&vcm_state_str) { + vcm_state_str_opt.and_then(|vcm_state_str| match serde_json::from_str(&vcm_state_str) { Ok(vcm_state) => Some(vcm_state), Err(err) => { error!("Error deserializing stored VCM state: {:?}", err); @@ -237,29 +260,52 @@ impl ViewContextManager { if self.contexts.is_empty() { self.reset(); - } else if self.active_context_ix >= self.contexts.len() { - self.active_context_ix = 0; } - self.contexts[self.active_context_ix].context.unhide(); + + if !self + .contexts + .iter() + .any(|vc| vc.id == self.active_context_id) + { + self.active_context_id = self.contexts[0].id; + } + + self.get_active_view_mut().unhide(); self.commit(); } /// Retrieves the active `ViewContextManager` pub fn get_active_view(&self) -> &dyn ViewContext { - &*self.contexts[self.active_context_ix].context + self + .contexts + .iter() + .find(|vc| vc.id == self.active_context_id) + .unwrap_or_else(|| { + panic!( + "Tried to get active VC with ID {} but it wasn't found", + self.active_context_id + ) + }) + .context + .as_ref() } /// Retrieves the active `ViewContextManager` pub fn get_active_view_mut(&mut self) -> &mut dyn ViewContext { - if self.contexts.len() <= self.active_context_ix { - panic!( - "Invalid VCM state; we only have {} VCs managed but active_context_ix is {}", - self.contexts.len(), - self.active_context_ix - ); - } - &mut *self.contexts[self.active_context_ix].context + let active_vc_id = self.active_context_id; + self + .contexts + .iter_mut() + .find(|vc| vc.id == active_vc_id) + .unwrap_or_else(|| { + panic!( + "Tried to get active VC with ID {} but it wasn't found", + active_vc_id + ) + }) + .context + .as_mut() } /// Updates the UI with an up-to-date listing of active view contexts and persist the current @@ -270,20 +316,45 @@ impl ViewContextManager { .iter() .map(|vc_entry| vc_entry.definition.clone()) .collect(); - let definitions_str = json::to_string(&minimal_view_context_definitions); - let connections_json = json::to_string(&self.connections); - let foreign_connectables_json = json::to_string(&self.foreign_connectables); + let definitions_str = serde_json::to_string(&minimal_view_context_definitions).unwrap(); + let connections_json = serde_json::to_string(&self.connections).unwrap(); + let foreign_connectables_json = serde_json::to_string(&self.foreign_connectables).unwrap(); js::init_view_contexts( - self.active_context_ix, + &self.active_context_id.to_string(), &definitions_str, &connections_json, &foreign_connectables_json, + &self.active_subgraph_id.to_string(), + serde_json::to_string(&self.subgraphs_by_id) + .unwrap() + .as_str(), ); self.save_all() } + /// Creates a new subgraph that contains a graph editor as its single view context + pub fn add_subgraph(&mut self) -> Uuid { + let new_subgraph_id = uuid_v4(); + + let new_graph_editor_vc_id = uuid_v4(); + self + .subgraphs_by_id + .insert(new_subgraph_id, SubgraphDescriptor { + id: new_subgraph_id, + name: format!("Subgraph {}", new_subgraph_id), + active_vc_id: new_graph_editor_vc_id, + }); + self.save_all(); + new_subgraph_id + } + + pub fn set_active_subgraph(&mut self, subgraph_id: Uuid) { + self.active_subgraph_id = subgraph_id; + self.save_all(); + } + pub fn get_vc_position(&self, id: Uuid) -> Option { self.contexts.iter().position(|vc_entry| vc_entry.id == id) } @@ -299,6 +370,13 @@ impl ViewContextManager { }, }; + let old_active_vc_id = self.active_context_id; + let old_active_vc_ix = self + .contexts + .iter() + .filter(|vc| vc.definition.subgraph_id == self.active_subgraph_id) + .position(|vc| vc.id == self.active_context_id); + let mut vc_entry = self.contexts.remove(ix); // Unrender it vc_entry.context.cleanup(); @@ -307,65 +385,105 @@ impl ViewContextManager { // Finally delete the VC entry for the VC itself js::delete_localstorage_key(&get_vc_key(id)); - let old_active_vc_ix = self.active_context_ix; - if self.active_context_ix == ix { - // If the deleted VC was the active VC, pick the one before it to be the active VC. - self.active_context_ix = ix.saturating_sub(1); - } else if self.active_context_ix > ix { - // If the active view context is above the one that was removed, shift it one down - self.active_context_ix = self.active_context_ix - 1; - } - - if let Some(vc_entry) = self.contexts.get_mut(self.active_context_ix) { - vc_entry.context.unhide(); + // If the deleted VC was the active VC, pick the one that's not at its old index to be active + if self.active_context_id == id { + match self + .contexts + .iter_mut() + .filter(|vc| vc.definition.subgraph_id == self.active_subgraph_id) + .nth(old_active_vc_ix.unwrap_or(0)) + { + Some(vc) => { + self.active_context_id = vc.id; + vc.context.unhide(); + }, + None => match self + .contexts + .iter_mut() + .filter(|vc| vc.definition.subgraph_id == self.active_subgraph_id) + .last() + { + Some(vc) => { + self.active_context_id = vc.id; + vc.context.unhide(); + }, + None => { + self.active_context_id = Uuid::nil(); + }, + }, + } } js::delete_view_context(&id.to_string()); - if old_active_vc_ix != self.active_context_ix { - self.get_active_view_mut().unhide(); - js::set_active_vc_ix(self.active_context_ix); + if self.active_context_id != old_active_vc_id { + js::set_active_vc_id(&self.active_context_id.to_string()); } self.save_all(); } - /// Serializes all managed view contexts and saves them to persistent storage. - pub fn save_all(&mut self) { - // TODO: Periodically call this, probably from inside of the VCMs themselves, in order - // to keep the state up to date. + fn serialize(&self) -> ViewContextManagerState { // TODO: Actually make use of the `touched` flag optimization here. let mut view_context_definitions = Vec::new(); let mut view_context_ids = Vec::new(); - for entry in &mut self.contexts { + for entry in &self.contexts { view_context_ids.push(entry.definition.uuid.clone()); let vc_id = entry.id; - let view_context_definition: ViewContextDefinition = entry.into(); + let view_context_definition: ViewContextDefinition = ViewContextDefinition { + minimal_def: entry.definition.clone(), + }; js::set_localstorage_key( &get_vc_key(vc_id), - &json::to_string(&view_context_definition), + &serde_json::to_string(&view_context_definition).unwrap(), ); view_context_definitions.push(view_context_definition); } - let state = ViewContextManagerState { + let mut subgraphs_by_id = self.subgraphs_by_id.clone(); + if let Some(subgraph_def) = subgraphs_by_id.get_mut(&self.active_subgraph_id) { + subgraph_def.active_vc_id = self.active_context_id; + } + + ViewContextManagerState { view_context_ids, - active_view_ix: self.active_context_ix, + deprecated_active_view_ix: 0, + active_view_id: self.active_context_id, patch_network_connections: self.connections.clone(), foreign_connectables: self.foreign_connectables.clone(), - }; + subgraphs_by_id, + active_subgraph_id: self.active_subgraph_id, + } + } + + /// Serializes all managed view contexts (and recursively those of all subgraphs) and saves them + /// to persistent storage. + /// + /// TODO: Periodically call this, probably from inside of the VCMs themselves, in order to keep + /// the state up to date. + pub fn save_all(&mut self) { + let state = self.serialize(); - let serialized_state: String = json::to_string(&state); + let serialized_state: String = serde_json::to_string(&state).unwrap(); js::set_localstorage_key(VCM_STATE_KEY, &serialized_state); } - pub fn set_active_view(&mut self, view_ix: usize) { + pub fn set_active_view(&mut self, view_id: Uuid) { self.save_all(); self.get_active_view_mut().hide(); - self.active_context_ix = view_ix; + self.active_context_id = view_id; + match self.subgraphs_by_id.get_mut(&self.active_subgraph_id) { + Some(subgraph) => subgraph.active_vc_id = view_id, + None => { + error!( + "Tried to set active view to vcId={view_id} but there's no subgraphId={}", + self.active_subgraph_id + ); + }, + } self.get_active_view_mut().unhide(); - js::set_active_vc_ix(view_ix); + js::set_active_vc_id(&view_id.to_string()); } pub fn set_connections( diff --git a/engine/note_container/Cargo.toml b/engine/note_container/Cargo.toml index 8107c0af..ffcac7d4 100644 --- a/engine/note_container/Cargo.toml +++ b/engine/note_container/Cargo.toml @@ -15,3 +15,4 @@ common = { path = "../common" } wbg_logging = { path = "../wbg_logging" } float-ord = "0.3" js-sys = "0.3" +fxhash = "0.2" diff --git a/engine/note_container/src/exports.rs b/engine/note_container/src/exports.rs index 2d0e5ac0..13a64530 100644 --- a/engine/note_container/src/exports.rs +++ b/engine/note_container/src/exports.rs @@ -1,6 +1,7 @@ -use std::{collections::HashMap, ops::Bound}; +use std::ops::Bound; use float_ord::FloatOrd; +use fxhash::FxHashMap; use js_sys::Function; use wasm_bindgen::prelude::*; @@ -157,7 +158,7 @@ pub fn iter_notes_with_cb( beat: f64, } - let mut unreleased_notes: HashMap = HashMap::default(); + let mut unreleased_notes: FxHashMap = FxHashMap::default(); let mut events: Vec = Vec::default(); let iter = notes.lines.iter().enumerate().flat_map(|(line_ix, line)| { line diff --git a/engine/spectrum_viz/Cargo.toml b/engine/spectrum_viz/Cargo.toml index a4f9be2a..852bcf5d 100644 --- a/engine/spectrum_viz/Cargo.toml +++ b/engine/spectrum_viz/Cargo.toml @@ -15,6 +15,7 @@ wasm-bindgen = { version = "=0.2.82", optional = true } # Disable logging staticly in release, making all log calls into no-ops log = { version = "0.4", features = ["release_max_level_off"] } wbg_logging = { path = "../wbg_logging", optional = true } +common = { path = "../common" } ndarray = { version = "0.15", optional = true, default-features = false, features = [ "std", diff --git a/engine/spectrum_viz/src/line_viz/mod.rs b/engine/spectrum_viz/src/line_viz/mod.rs index 97706c5a..9022ee88 100644 --- a/engine/spectrum_viz/src/line_viz/mod.rs +++ b/engine/spectrum_viz/src/line_viz/mod.rs @@ -1,6 +1,7 @@ use canvas_utils::VizView; use self::{conf::FFT_BUFFER_SIZE, viz::LineSpectrumCtx}; +use common::ref_static_mut; pub(self) mod conf; pub(crate) mod cubic_spline; @@ -51,7 +52,7 @@ static mut CTX: LineSpectrumCtx = LineSpectrumCtx { image_data_buf: Vec::new(), }; -fn ctx() -> &'static mut LineSpectrumCtx { unsafe { &mut CTX } } +fn ctx() -> &'static mut LineSpectrumCtx { ref_static_mut!(CTX) } #[no_mangle] pub extern "C" fn line_spectrogram_set_view(width_px: usize, height_px: usize, dpr: usize) { diff --git a/package.json b/package.json index bd3142e7..82cee255 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "build": "rm -rf dist/* && NODE_OPTIONS=--experimental-worker BACKEND_BASE_URL=\"https://web-synth-api.ameo.design\" webpack --config webpack.prod.js && cp -r ./public/* ./dist", "build-headless": "rm -rf dist/headless/* && NODE_OPTIONS=--experimental-worker BACKEND_BASE_URL=\"https://web-synth-api.ameo.design\" webpack --config webpack.headless.js && cp -r ./public/* ./dist/headless", "lint": "eslint \"./src/**/*.@(ts|tsx|js|jsx)\"", + "typecheck": "tsc --noEmit", "prettier": "prettier --write \"src/**/*.{ts,js,tsx}\"", "cypress:open": "cypress open", "cypress:run": "cypress run --browser chrome --record --key 58905cf6-d1cb-43de-b6cd-b55954039c1a", diff --git a/src/ViewContextManager/VcHideStatusRegistry.ts b/src/ViewContextManager/VcHideStatusRegistry.ts index 8a6695d3..507bbc01 100644 --- a/src/ViewContextManager/VcHideStatusRegistry.ts +++ b/src/ViewContextManager/VcHideStatusRegistry.ts @@ -43,8 +43,9 @@ export const getIsVcHidden = (vcId: string): boolean => { return false; } const state = mainReduxGetState(); - const vc = - state.viewContextManager.activeViewContexts[state.viewContextManager.activeViewContextIx]; + const vc = state.viewContextManager.activeViewContexts.find( + vc => vc.uuid === state.viewContextManager.activeViewContextId + ); if (!vc) { console.warn( `Tried to check if vcId=${vcId} is hidden, but no active VC was found in Redux state` diff --git a/src/ViewContextManager/ViewContextManager.tsx b/src/ViewContextManager/ViewContextManager.tsx index 3a404ffe..aac8508d 100644 --- a/src/ViewContextManager/ViewContextManager.tsx +++ b/src/ViewContextManager/ViewContextManager.tsx @@ -164,7 +164,6 @@ interface ViewContextTabProps { uuid: string; title?: string; active: boolean; - i: number; } interface VCMTabRenamerProps { @@ -274,10 +273,11 @@ interface ViewContextSwitcherProps { * backend every time there is a change. */ export const ViewContextSwitcher: React.FC = ({ engine }) => { - const { activeViewContexts, activeViewContextIx } = useSelector( + const { activeViewContexts, activeViewContextId, activeSubgraphID } = useSelector( (state: ReduxStore) => ({ activeViewContexts: state.viewContextManager.activeViewContexts, - activeViewContextIx: state.viewContextManager.activeViewContextIx, + activeViewContextId: state.viewContextManager.activeViewContextId, + activeSubgraphID: state.viewContextManager.activeSubgraphID, }), shallowEqual ); @@ -295,15 +295,16 @@ export const ViewContextSwitcher: React.FC = ({ engine }} onWheel={handleScroll} > - {activeViewContexts.map((props, i) => ( - - ))} + {activeViewContexts + .filter(vc => vc.subgraphId === activeSubgraphID) + .map(props => ( + + ))} ); }; diff --git a/src/controls/GenericPresetPicker/GenericPresetSaver.tsx b/src/controls/GenericPresetPicker/GenericPresetSaver.tsx index b54fe27d..bbe81e43 100644 --- a/src/controls/GenericPresetPicker/GenericPresetSaver.tsx +++ b/src/controls/GenericPresetPicker/GenericPresetSaver.tsx @@ -40,7 +40,9 @@ interface TagPickerProps { const TagPicker: React.FC = ({ getExistingTags, value, onChange }) => { const { data: existingTags, error } = useQuery('existingTags', () => - getExistingTags().then(tags => R.sortWith([R.prop('name'), tag => tag.count ?? 0], tags)) + getExistingTags().then(tags => + R.sortWith([R.ascend(R.prop('name')), tag => tag.count ?? 0], tags) + ) ); const [enteredTagName, setEnteredTagNameInner] = useState(''); const staticEnteredTagName = useRef(''); diff --git a/src/graphEditor/GraphEditor.tsx b/src/graphEditor/GraphEditor.tsx index 5155e483..d041675f 100644 --- a/src/graphEditor/GraphEditor.tsx +++ b/src/graphEditor/GraphEditor.tsx @@ -374,8 +374,8 @@ const GraphEditor: React.FC<{ stateKey: string }> = ({ stateKey }) => { setLGraphHandle(vcId, lGraphInstance); // If the graph editor isn't visible, make sure we stop its rendering to save resources - const { activeViewContexts, activeViewContextIx } = getState().viewContextManager; - const activeVC = activeViewContexts[activeViewContextIx]; + const { activeViewContexts, activeViewContextId } = getState().viewContextManager; + const activeVC = activeViewContexts.find(vc => vc.uuid === activeViewContextId); if (!activeVC) { console.error('No active view context'); return; diff --git a/src/graphEditor/nodes/CustomAudio/ScaleAndShift/ResponsePlot.svelte b/src/graphEditor/nodes/CustomAudio/ScaleAndShift/ResponsePlot.svelte index c61e697f..8edc297e 100644 --- a/src/graphEditor/nodes/CustomAudio/ScaleAndShift/ResponsePlot.svelte +++ b/src/graphEditor/nodes/CustomAudio/ScaleAndShift/ResponsePlot.svelte @@ -1,8 +1,8 @@