From ca8c514185c1b5bb22aec752a4c41da0a50dcd4d Mon Sep 17 00:00:00 2001 From: Mason Jones Date: Thu, 8 Feb 2024 16:21:32 -0800 Subject: [PATCH] Get linux and windows builds working --- build.sh | 40 +++++++++++++++++++ vpo-backend/.cargo/config.toml | 11 +---- vpo-backend/Cargo.lock | 7 ++++ vpo-backend/Cargo.toml | 2 +- vpo-backend/ipc/src/file_server.rs | 16 ++++++-- vpo-backend/node-engine/src/errors.rs | 23 ++++++++++- .../src/node/calculate_traversal_order.rs | 9 +++++ vpo-backend/node-engine/src/node/mod.rs | 2 +- .../node-engine/src/nodes/rank_player.rs | 38 +++++++++--------- vpo-backend/src/io/mod.rs | 39 ++++++++++++------ vpo-backend/src/lib.rs | 6 --- vpo-backend/src/main.rs | 10 +++-- vpo-backend/src/routes.rs | 1 - vpo-backend/src/routes/graph/commit.rs | 19 ++++----- vpo-backend/src/routes/graph/paste.rs | 19 ++++----- vpo-backend/src/routes/graph/redo.rs | 20 +++++----- vpo-backend/src/routes/graph/undo.rs | 20 +++++----- .../src/routes/graph/update_node_state.rs | 9 +++-- vpo-backend/src/routes/io/load.rs | 25 ++++++------ vpo-backend/src/routes/io/save.rs | 5 +-- vpo-backend/src/routes/prelude.rs | 34 +++++++++++++--- 21 files changed, 234 insertions(+), 121 deletions(-) create mode 100755 build.sh diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..14f234eb --- /dev/null +++ b/build.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +ROOT=$(pwd) +VERSION="v0.1.0-alpha" + +# part 0, make sure targets are present +rustup target add x86_64-unknown-linux-gnu +rustup target add x86_64-pc-windows-gnu + +# part 1, setup +mkdir -p build +rm -Rf build/* +mkdir -p build/linux +mkdir -p build/windows + +# part 2, build the backend +cd "$ROOT/vpo-backend" + +cargo build --release +cargo build --release --target x86_64-pc-windows-gnu + +cp target/release/vpo-backend $ROOT/build/linux +cp target/x86_64-pc-windows-gnu/release/vpo-backend.exe $ROOT/build/windows + +# part 3, build the frontend +cd $ROOT/vpo-frontend + +npm run build + +cp -R build $ROOT/build/linux/frontend +cp -R build $ROOT/build/windows/frontend + +# part 4, package everything up +cd $ROOT/build/linux +tar -czvf "mjuo-linux-$VERSION.tar.gz" ./* +mv "mjuo-linux-$VERSION.tar.gz" .. + +cd $ROOT/build/windows +zip -r "mjuo-windows-$VERSION.zip" . +mv "mjuo-windows-$VERSION.zip" .. \ No newline at end of file diff --git a/vpo-backend/.cargo/config.toml b/vpo-backend/.cargo/config.toml index 847b3a7e..188308db 100644 --- a/vpo-backend/.cargo/config.toml +++ b/vpo-backend/.cargo/config.toml @@ -1,11 +1,2 @@ -[target.wasm32-unknown-unknown] -rustflags = [ - "-C", - "link-arg=--initial-memory=327680000", -] - [target.x86_64-unknown-linux-gnu] -rustflags = [ - "-C", - "link-arg=-fuse-ld=lld", -] +rustflags = ["-C", "link-arg=-fuse-ld=lld"] diff --git a/vpo-backend/Cargo.lock b/vpo-backend/Cargo.lock index 7781cbba..aa483120 100644 --- a/vpo-backend/Cargo.lock +++ b/vpo-backend/Cargo.lock @@ -1829,6 +1829,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -3075,6 +3081,7 @@ dependencies = [ "notify", "notify-debouncer-mini", "once_cell", + "path-slash", "petgraph", "rayon", "regex", diff --git a/vpo-backend/Cargo.toml b/vpo-backend/Cargo.toml index 3ece4915..ad4db07d 100644 --- a/vpo-backend/Cargo.toml +++ b/vpo-backend/Cargo.toml @@ -44,6 +44,7 @@ ddgg = { git = "https://github.com/smj-edison/ddgg", features = [ "serde", "serde_string_indexes", ] } +path-slash = "0.2.1" [lib] crate-type = ["cdylib", "lib"] @@ -69,7 +70,6 @@ walkdir = "2" [profile.release] -lto = "thin" opt-level = 3 [profile.dev] diff --git a/vpo-backend/ipc/src/file_server.rs b/vpo-backend/ipc/src/file_server.rs index 193c081a..e9daab21 100644 --- a/vpo-backend/ipc/src/file_server.rs +++ b/vpo-backend/ipc/src/file_server.rs @@ -6,17 +6,25 @@ use std::{ use tokio::runtime; use tower_http::services::ServeDir; -pub fn start_file_server(root_folder: flume::Receiver) -> JoinHandle<()> { +pub fn start_file_server_in(root_folder: PathBuf, port: u16) -> JoinHandle<()> { + let (sender, receiver) = flume::unbounded(); + sender.send(root_folder).unwrap(); + + start_file_server(receiver, port) +} + +pub fn start_file_server(root_folder: flume::Receiver, port: u16) -> JoinHandle<()> { thread::spawn(move || { let rt = runtime::Builder::new_current_thread().enable_io().build().unwrap(); rt.block_on(async { + // wait for the first folder let mut project_dir = root_folder.recv_async().await.expect("not closed"); loop { let service = ServeDir::new(project_dir.clone()); - let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 26643)); + let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port)); let server = async { hyper::Server::bind(&addr) .serve(tower::make::Shared::new(service)) @@ -26,8 +34,8 @@ pub fn start_file_server(root_folder: flume::Receiver) -> JoinHandle<() // use select to terminate the server early tokio::select! { - new_project_dir = root_folder.recv_async() => { - project_dir = new_project_dir.expect("not closed"); + Ok(new_project_dir) = root_folder.recv_async() => { + project_dir = new_project_dir; // if a new project dir comes in, it'll drop the file server } _ = server => { diff --git a/vpo-backend/node-engine/src/errors.rs b/vpo-backend/node-engine/src/errors.rs index f5858eec..b00b9acb 100644 --- a/vpo-backend/node-engine/src/errors.rs +++ b/vpo-backend/node-engine/src/errors.rs @@ -1,6 +1,7 @@ use common::resource_manager::ResourceId; use ddgg::GraphError; use rhai::{EvalAltResult, ParseError}; +use serde::Serialize; use snafu::Snafu; use crate::connection::{Socket, SocketType}; @@ -74,6 +75,15 @@ impl From for NodeError { } } +impl Serialize for NodeError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + #[derive(Snafu, Debug)] pub enum NodeWarning { #[snafu(display("Value of type `{return_type}` was returned, ignoring"))] @@ -84,11 +94,22 @@ pub enum NodeWarning { RhaiParserFailure { parser_error: ParseError }, #[snafu(display("Internal node errors/warnings: {errors_and_warnings:?}"))] InternalErrorsAndWarnings { errors_and_warnings: ErrorsAndWarnings }, + #[snafu(display("Resource missing: {resource:?}"))] + ResourceMissing { resource: ResourceId }, +} + +impl Serialize for NodeWarning { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } } pub type NodeResult = Result, NodeError>; -#[derive(Debug, Default)] +#[derive(Debug, Default, Serialize)] pub struct ErrorsAndWarnings { pub errors: Vec<(NodeIndex, NodeError)>, pub warnings: Vec<(NodeIndex, NodeWarning)>, diff --git a/vpo-backend/node-engine/src/node/calculate_traversal_order.rs b/vpo-backend/node-engine/src/node/calculate_traversal_order.rs index 19f26d07..f8a0735e 100644 --- a/vpo-backend/node-engine/src/node/calculate_traversal_order.rs +++ b/vpo-backend/node-engine/src/node/calculate_traversal_order.rs @@ -198,6 +198,15 @@ pub fn generate_io_spec( for needed_resource in &needed_resources { let resource_index = resources.get_resource_index(needed_resource); + if resource_index.is_none() { + warnings.push(( + *node_index, + NodeWarning::ResourceMissing { + resource: needed_resource.clone(), + }, + )); + } + resources_tracking.push((needed_resource.clone(), resource_index)); } diff --git a/vpo-backend/node-engine/src/node/mod.rs b/vpo-backend/node-engine/src/node/mod.rs index ed7e2566..a89619e1 100644 --- a/vpo-backend/node-engine/src/node/mod.rs +++ b/vpo-backend/node-engine/src/node/mod.rs @@ -81,7 +81,7 @@ impl NodeIo { } } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct InitResult { pub changed_properties: Option>, pub needed_resources: Vec, diff --git a/vpo-backend/node-engine/src/nodes/rank_player.rs b/vpo-backend/node-engine/src/nodes/rank_player.rs index bab3c740..5ca03bef 100644 --- a/vpo-backend/node-engine/src/nodes/rank_player.rs +++ b/vpo-backend/node-engine/src/nodes/rank_player.rs @@ -29,24 +29,26 @@ impl NodeRuntime for RankPlayerNode { self.polyphony = polyphony as usize; } - let rank_resource = params - .props - .get("rank") - .and_then(|x| x.clone().as_resource()) - .and_then(|resource_id| { - params - .resources - .ranks - .borrow_resource_by_id(&resource_id.resource) - .map(|resource| (resource_id.clone(), resource)) - }); - - let needed_resources = if let Some((id, rank)) = rank_resource { - let (player, needed_resources) = RankPlayer::new(id, rank, self.polyphony, params.sound_config.sample_rate); - - self.player = player; - - needed_resources + let rank_resource_id = params.props.get("rank").and_then(|x| x.clone().as_resource()); + + let rank = rank_resource_id + .as_ref() + .and_then(|resource_id| params.resources.ranks.borrow_resource_by_id(&resource_id.resource)); + + let needed_resources = if let Some(resource_id) = rank_resource_id { + if let Some(rank) = rank { + let (player, needed_resources) = + RankPlayer::new(resource_id, rank, self.polyphony, params.sound_config.sample_rate); + + self.player = player; + + needed_resources + } else { + return Ok(NodeOk { + value: InitResult::default(), + warnings: vec![NodeWarning::ResourceMissing { resource: resource_id }], + }); + } } else { vec![] }; diff --git a/vpo-backend/src/io/mod.rs b/vpo-backend/src/io/mod.rs index 05aa634b..88ffe6b4 100644 --- a/vpo-backend/src/io/mod.rs +++ b/vpo-backend/src/io/mod.rs @@ -15,6 +15,7 @@ use log::info; use node_engine::resources::Resources; use node_engine::state::GraphState; use notify::{Config, Error, Event, RecommendedWatcher, RecursiveMode, Watcher}; +use path_slash::PathExt; use rayon::iter::{ParallelBridge, ParallelIterator}; use semver::Version; use serde_json::{json, Value}; @@ -83,7 +84,11 @@ where None }) .map(|asset| { - let asset_key = asset.path().strip_prefix(path).unwrap().to_string_lossy().to_string(); + let asset_path = asset.path().strip_prefix(path).unwrap(); + let asset_key = get_resource_key(asset_path); + + println!("path: {asset_path:?}, key: {asset_key}"); + (asset_key, PathBuf::from(asset.path())) }); @@ -115,34 +120,34 @@ pub fn load_single( .whatever_context(format!("Could not strip \"{:?}\" of \"{:?}\"", file, root))?; let resource_type = relative_file.iter().next().unwrap(); - let resource = relative_file.strip_prefix(resource_type).unwrap().to_string_lossy(); + let resource_key = get_resource_key(relative_file.strip_prefix(resource_type).unwrap()); - info!("loading resource: `{:?}` of type {:?}", resource, resource_type); + info!("loading resource: `{:?}` of type {:?}", resource_key, resource_type); match resource_type.to_string_lossy().as_ref() { "ranks" => { - if resources.ranks.get_index(resource.as_ref()).is_some() { - resources.ranks.remove_resource(resource.as_ref()); + if resources.ranks.get_index(resource_key.as_ref()).is_some() { + resources.ranks.remove_resource(resource_key.as_ref()); } let rank = load_rank_from_file(file, &resources.samples)?; - resources.ranks.add_resource(resource.into_owned(), rank); + resources.ranks.add_resource(resource_key, rank); } "samples" => { - if resources.samples.get_index(resource.as_ref()).is_some() { - resources.samples.remove_resource(resource.as_ref()); + if resources.samples.get_index(resource_key.as_ref()).is_some() { + resources.samples.remove_resource(resource_key.as_ref()); } let sample = load_sample(file, &config)?; - resources.samples.add_resource(resource.into_owned(), sample); + resources.samples.add_resource(resource_key, sample); } "ui" => { - if resources.ui.get_index(resource.as_ref()).is_some() { - resources.ui.remove_resource(resource.as_ref()); + if resources.ui.get_index(resource_key.as_ref()).is_some() { + resources.ui.remove_resource(resource_key.as_ref()); } let ui_element = load_ui_from_file(file)?; - resources.ui.add_resource(resource.into_owned(), ui_element); + resources.ui.add_resource(resource_key, ui_element); } _ => {} } @@ -212,3 +217,13 @@ pub fn load_state( Ok(rx) } + +fn get_resource_key(path: &Path) -> String { + #[cfg(windows)] + let asset_key = path.to_slash_lossy().to_string(); + + #[cfg(unix)] + let asset_key = path.to_string_lossy().to_string(); + + asset_key +} diff --git a/vpo-backend/src/lib.rs b/vpo-backend/src/lib.rs index 5a3dd5cc..a7db82b8 100644 --- a/vpo-backend/src/lib.rs +++ b/vpo-backend/src/lib.rs @@ -64,12 +64,6 @@ pub async fn handle_msg( match result { Ok(route_result) => { - if !route_result.engine_updates.is_empty() { - for update in route_result.engine_updates { - to_audio_thread.send(update).unwrap(); - } - } - if route_result.new_project { let _ = project_dir_sender.send( global_state diff --git a/vpo-backend/src/main.rs b/vpo-backend/src/main.rs index 56221fa0..edbf229f 100644 --- a/vpo-backend/src/main.rs +++ b/vpo-backend/src/main.rs @@ -1,4 +1,5 @@ use std::cell::RefCell; +use std::path::PathBuf; use std::sync::{Arc, RwLock}; use std::thread; @@ -7,7 +8,7 @@ use futures::executor::LocalPool; use futures::join; use futures::task::LocalSpawnExt; use futures::StreamExt; -use ipc::file_server::start_file_server; +use ipc::file_server::{start_file_server, start_file_server_in}; use node_engine::resources::Resources; use node_engine::state::{FromNodeEngine, GraphState}; @@ -22,7 +23,7 @@ use vpo_backend::util::{send_graph_updates, send_resource_updates}; use vpo_backend::{handle_msg, start_ipc}; fn main() { - env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init(); let (to_server, from_server, _ipc_handle) = start_ipc(26642); @@ -35,7 +36,10 @@ fn main() { let (project_dir_sender, project_dir_receiver) = flume::unbounded(); let (mut file_watcher, mut from_file_watcher) = FileWatcher::new().unwrap(); - let _file_server_handle = start_file_server(project_dir_receiver); + let _project_files_handle = start_file_server(project_dir_receiver, 26643); + let _frontend_server_handle = start_file_server_in(PathBuf::from("./frontend"), 26644); + + println!("Welcome to MJUO! Please go to http://localhost:26644 in your browser to see the UI"); let resources_for_audio_thread = resources.clone(); diff --git a/vpo-backend/src/routes.rs b/vpo-backend/src/routes.rs index 91fb5524..9ed16552 100644 --- a/vpo-backend/src/routes.rs +++ b/vpo-backend/src/routes.rs @@ -13,7 +13,6 @@ use serde_json::Value; use crate::{engine::ToAudioThread, errors::EngineError, state::GlobalState, Sender}; #[derive(Default)] pub struct RouteReturn { - pub engine_updates: Vec, pub new_project: bool, } diff --git a/vpo-backend/src/routes/graph/commit.rs b/vpo-backend/src/routes/graph/commit.rs index 4c1a0f26..e0c6a1c5 100644 --- a/vpo-backend/src/routes/graph/commit.rs +++ b/vpo-backend/src/routes/graph/commit.rs @@ -51,13 +51,14 @@ pub fn route(mut state: RouteState) -> Result { send_project_state_updates(state.state, state.global_state, state.to_server)?; } - Ok(RouteReturn { - engine_updates: state_invalidations( - state.state, - invalidations, - &mut state.global_state.device_manager, - &*state.resources_lock.read().unwrap(), - )?, - new_project: false, - }) + state_invalidations( + state.state, + invalidations, + &mut state.global_state.device_manager, + &*state.resources_lock.read().unwrap(), + state.to_audio_thread, + state.to_server, + )?; + + Ok(RouteReturn { new_project: false }) } diff --git a/vpo-backend/src/routes/graph/paste.rs b/vpo-backend/src/routes/graph/paste.rs index 79c1a012..ad75d163 100644 --- a/vpo-backend/src/routes/graph/paste.rs +++ b/vpo-backend/src/routes/graph/paste.rs @@ -129,13 +129,14 @@ pub fn route(mut state: RouteState) -> Result { send_graph_updates(state.state, *graph_index, state.to_server)?; } - Ok(RouteReturn { - engine_updates: state_invalidations( - state.state, - invalidations, - &mut state.global_state.device_manager, - &*state.resources_lock.read().unwrap(), - )?, - new_project: false, - }) + state_invalidations( + state.state, + invalidations, + &mut state.global_state.device_manager, + &*state.resources_lock.read().unwrap(), + state.to_audio_thread, + state.to_server, + )?; + + Ok(RouteReturn { new_project: false }) } diff --git a/vpo-backend/src/routes/graph/redo.rs b/vpo-backend/src/routes/graph/redo.rs index c2d610ac..39ee88c8 100644 --- a/vpo-backend/src/routes/graph/redo.rs +++ b/vpo-backend/src/routes/graph/redo.rs @@ -33,14 +33,14 @@ pub fn route(state: RouteState) -> Result { } send_project_state_updates(state.state, state.global_state, state.to_server)?; - - Ok(RouteReturn { - engine_updates: state_invalidations( - state.state, - invalidations, - &mut state.global_state.device_manager, - &*state.resources_lock.read().unwrap(), - )?, - new_project: false, - }) + state_invalidations( + state.state, + invalidations, + &mut state.global_state.device_manager, + &*state.resources_lock.read().unwrap(), + state.to_audio_thread, + state.to_server, + )?; + + Ok(RouteReturn { new_project: false }) } diff --git a/vpo-backend/src/routes/graph/undo.rs b/vpo-backend/src/routes/graph/undo.rs index db496dee..c8090d19 100644 --- a/vpo-backend/src/routes/graph/undo.rs +++ b/vpo-backend/src/routes/graph/undo.rs @@ -33,14 +33,14 @@ pub fn route(state: RouteState) -> Result { } send_project_state_updates(state.state, state.global_state, state.to_server)?; - - Ok(RouteReturn { - engine_updates: state_invalidations( - state.state, - invalidations, - &mut state.global_state.device_manager, - &*state.resources_lock.read().unwrap(), - )?, - new_project: false, - }) + state_invalidations( + state.state, + invalidations, + &mut state.global_state.device_manager, + &*state.resources_lock.read().unwrap(), + state.to_audio_thread, + state.to_server, + )?; + + Ok(RouteReturn { new_project: false }) } diff --git a/vpo-backend/src/routes/graph/update_node_state.rs b/vpo-backend/src/routes/graph/update_node_state.rs index 244ed009..c4c9abeb 100644 --- a/vpo-backend/src/routes/graph/update_node_state.rs +++ b/vpo-backend/src/routes/graph/update_node_state.rs @@ -17,9 +17,10 @@ struct Payload { pub fn route(mut state: RouteState) -> Result { let payload: Payload = serde_json::from_value(state.msg["payload"].take()).context(JsonParserSnafu)?; + state + .to_audio_thread + .send(ToAudioThread::NewNodeStates(payload.updated_states)) + .unwrap(); - Ok(RouteReturn { - engine_updates: vec![ToAudioThread::NewNodeStates(payload.updated_states)], - new_project: false, - }) + Ok(RouteReturn { new_project: false }) } diff --git a/vpo-backend/src/routes/io/load.rs b/vpo-backend/src/routes/io/load.rs index c6daf27a..21343a41 100644 --- a/vpo-backend/src/routes/io/load.rs +++ b/vpo-backend/src/routes/io/load.rs @@ -17,7 +17,7 @@ use crate::{ util::{send_graph_updates, send_project_state_updates, send_resource_updates}, }; -pub async fn route<'a>(mut ctx: RouteState<'a>) -> Result { +pub async fn route(mut ctx: RouteState<'_>) -> Result { let file = AsyncFileDialog::new().pick_file().await; let resources = &mut *ctx.resources_lock.write().unwrap(); @@ -42,7 +42,7 @@ pub async fn route<'a>(mut ctx: RouteState<'a>) -> Result(mut ctx: RouteState<'a>) -> Result(state: RouteState<'a>) -> Result, device_manager: &mut DeviceManager, resources: &Resources, -) -> Result, EngineError> { + to_audio_thread: &flume::Sender, + client_sender: &flume::Sender, +) -> Result<(), EngineError> { + let mut to_client = vec![]; let mut new_engine_needed = false; let mut new_defaults = vec![]; let mut updates = vec![]; @@ -245,16 +250,33 @@ pub fn state_invalidations( } if new_engine_needed { - updates.push(ToAudioThread::NewTraverser( - state.create_traverser(resources).context(NodeSnafu)?.1, - )); + let (errors_and_warnings, traverser) = state.create_traverser(resources).context(NodeSnafu)?; + + if errors_and_warnings.any() { + warn!("Traverser warnings: {:?}", errors_and_warnings); + + to_client.push(IpcMessage::Json(json!({ + "action": "graph/errorsAndWarnings", + "payload": errors_and_warnings + }))); + } + + updates.push(ToAudioThread::NewTraverser(traverser)); } if !new_defaults.is_empty() { updates.push(ToAudioThread::NewDefaults(new_defaults)); } - Ok(updates) + for update in updates { + to_audio_thread.send(update).unwrap(); + } + + for message in to_client { + client_sender.send(message).unwrap(); + } + + Ok(()) } fn calculate_device_channels(