diff --git a/Cargo.lock b/Cargo.lock index 57420a5a7..df363e216 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -940,6 +940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -948,8 +949,22 @@ version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.99", ] [[package]] @@ -2428,6 +2443,7 @@ dependencies = [ "bezier-rs", "bitflags 2.9.0", "chrono", + "clap", "dyn-any", "fern", "futures", diff --git a/node-graph/graphene-cli/Cargo.toml b/node-graph/graphene-cli/Cargo.toml index 7a1496648..42ccaabd4 100644 --- a/node-graph/graphene-cli/Cargo.toml +++ b/node-graph/graphene-cli/Cargo.toml @@ -45,6 +45,9 @@ image = { workspace = true, default-features = false, features = [ "png", ] } +# Required dependencies +clap = { version = "4.5.31", features = ["cargo", "derive"] } + # Optional local dependencies wgpu-executor = { path = "../wgpu-executor", optional = true } gpu-executor = { path = "../gpu-executor", optional = true } diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index 6b3f85e34..fa10cba0d 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -1,5 +1,6 @@ use graph_craft::document::*; use graph_craft::graphene_compiler::{Compiler, Executor}; +use graph_craft::proto::ProtoNetwork; use graph_craft::util::load_network; use graph_craft::wasm_application_io::EditorPreferences; use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateSender}; @@ -7,10 +8,11 @@ use graphene_core::text::FontCache; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use interpreted_executor::dynamic_executor::DynamicExecutor; +use clap::{Args, Parser, Subcommand}; use fern::colors::{Color, ColoredLevelConfig}; use futures::executor::block_on; use interpreted_executor::util::wrap_network_in_scope; -use std::{error::Error, sync::Arc}; +use std::{error::Error, path::PathBuf, sync::Arc}; struct UpdateLogger {} @@ -20,57 +22,134 @@ impl NodeGraphUpdateSender for UpdateLogger { } } +#[derive(Debug, Parser)] +#[clap(name = "graphene-cli", version)] +pub struct App { + #[clap(flatten)] + global_opts: GlobalOpts, + + #[clap(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Help message for compile. + Compile { + /// Print proto network + #[clap(long, short = 'p')] + print_proto: bool, + + /// Path to the .graphite document + document: PathBuf, + }, + /// Help message for run. + Run { + /// Path to the .graphite document + document: PathBuf, + + /// Path to the .graphite document + image: Option, + + /// Run the document in a loop. This is useful for spawning and maintaining a window + #[clap(long, short = 'l')] + run_loop: bool, + }, +} + +#[derive(Debug, Args)] +struct GlobalOpts { + /// Verbosity level (can be specified multiple times) + #[clap(long, short, global = true, action = clap::ArgAction::Count)] + verbose: u8, +} + #[tokio::main] async fn main() -> Result<(), Box> { - init_logging(); + let app = App::parse(); - let document_path = std::env::args().nth(1).expect("No document path provided"); + let log_level = app.global_opts.verbose; - let image_path = std::env::args().nth(2); + init_logging(log_level); - let document_string = std::fs::read_to_string(&document_path).expect("Failed to read document"); + let document_path = match app.command { + Command::Compile { ref document, .. } => document, + Command::Run { ref document, .. } => document, + }; - println!("creating gpu context",); + let document_string = std::fs::read_to_string(document_path).expect("Failed to read document"); + + log::info!("creating gpu context",); let mut application_io = block_on(WasmApplicationIo::new()); - if let Some(image_path) = image_path { + + if let Command::Run { image: Some(ref image_path), .. } = app.command { application_io.resources.insert("null".to_string(), Arc::from(std::fs::read(image_path).expect("Failed to read image"))); } - let device = application_io.gpu_executor().unwrap().context.device.clone(); - std::thread::spawn(move || loop { - std::thread::sleep(std::time::Duration::from_nanos(10)); - device.poll(wgpu::Maintain::Poll); - }); + let preferences = EditorPreferences { + use_vello: true, + ..Default::default() + }; let editor_api = Arc::new(WasmEditorApi { font_cache: FontCache::default(), application_io: Some(application_io.into()), node_graph_message_sender: Box::new(UpdateLogger {}), - editor_preferences: Box::new(EditorPreferences::default()), + editor_preferences: Box::new(preferences), }); - let executor = create_executor(document_string, editor_api)?; - let render_config = graphene_core::application_io::RenderConfig::default(); + let proto_graph = compile_graph(document_string, editor_api)?; - loop { - let _result = (&executor).execute(render_config).await?; - std::thread::sleep(std::time::Duration::from_millis(16)); + match app.command { + Command::Compile { print_proto, .. } => { + if print_proto { + println!("{}", proto_graph); + } + } + Command::Run { run_loop, .. } => { + std::thread::spawn(move || loop { + std::thread::sleep(std::time::Duration::from_nanos(10)); + device.poll(wgpu::Maintain::Poll); + }); + let executor = create_executor(proto_graph)?; + let render_config = graphene_core::application_io::RenderConfig::default(); + + loop { + let result = (&executor).execute(render_config).await?; + if !run_loop { + println!("{:?}", result); + break; + } + std::thread::sleep(std::time::Duration::from_millis(16)); + } + } } + + Ok(()) } -fn init_logging() { +fn init_logging(log_level: u8) { + let default_level = match log_level { + 0 => log::LevelFilter::Error, + 1 => log::LevelFilter::Info, + 2 => log::LevelFilter::Debug, + _ => log::LevelFilter::Trace, + }; let colors = ColoredLevelConfig::new().debug(Color::Magenta).info(Color::Green).error(Color::Red); fern::Dispatch::new() .chain(std::io::stdout()) - .level_for("iced", log::LevelFilter::Trace) - .level_for("wgpu", log::LevelFilter::Debug) - .level(log::LevelFilter::Trace) + .level_for("wgpu", log::LevelFilter::Error) + .level_for("naga", log::LevelFilter::Error) + .level_for("wgpu_hal", log::LevelFilter::Error) + .level_for("wgpu_core", log::LevelFilter::Error) + .level(default_level) .format(move |out, message, record| { out.finish(format_args!( - "[{}]{} {}", + "[{}]{}{} {}", // This will color the log level only, not the whole line. Just a touch. colors.color(record.level()), chrono::Utc::now().format("[%Y-%m-%d %H:%M:%S]"), + record.module_path().unwrap_or(""), message )) }) @@ -98,53 +177,16 @@ fn fix_nodes(network: &mut NodeNetwork) { } } } - -fn create_executor(document_string: String, editor_api: Arc) -> Result> { +fn compile_graph(document_string: String, editor_api: Arc) -> Result> { let mut network = load_network(&document_string); fix_nodes(&mut network); let wrapped_network = wrap_network_in_scope(network.clone(), editor_api); let compiler = Compiler {}; - let protograph = compiler.compile_single(wrapped_network)?; - let executor = block_on(DynamicExecutor::new(protograph)).unwrap(); - Ok(executor) + compiler.compile_single(wrapped_network).map_err(|x| x.into()) } -// #[cfg(test)] -// mod test { -// use super::*; - -// #[tokio::test] -// #[cfg_attr(not(feature = "wayland"), ignore)] -// async fn grays_scale() { -// let document_string = include_str!("../test_files/gray.graphite"); -// let executor = create_executor(document_string.to_string()).unwrap(); -// let editor_api = WasmEditorApi { -// image_frame: None, -// font_cache: &FontCache::default(), -// application_io: &block_on(WasmApplicationIo::new()), -// node_graph_message_sender: &UpdateLogger {}, -// editor_preferences: &EditorPreferences::default(), -// render_config: graphene_core::application_io::RenderConfig::default(), -// }; -// let result = (&executor).execute(editor_api.clone()).await.unwrap(); -// println!("result: {result:?}"); -// } - -// #[tokio::test] -// #[cfg_attr(not(feature = "wayland"), ignore)] -// async fn hue() { -// let document_string = include_str!("../test_files/hue.graphite"); -// let executor = create_executor(document_string.to_string()).unwrap(); -// let editor_api = WasmEditorApi { -// image_frame: None, -// font_cache: &FontCache::default(), -// application_io: &block_on(WasmApplicationIo::new()), -// node_graph_message_sender: &UpdateLogger {}, -// editor_preferences: &EditorPreferences::default(), -// render_config: graphene_core::application_io::RenderConfig::default(), -// }; -// let result = (&executor).execute(editor_api.clone()).await.unwrap(); -// println!("result: {result:?}"); -// } -// } +fn create_executor(proto_network: ProtoNetwork) -> Result> { + let executor = block_on(DynamicExecutor::new(proto_network)).map_err(|errors| errors.iter().map(|e| format!("{e:?}")).reduce(|acc, e| format!("{acc}\n{e}")).unwrap_or_default())?; + Ok(executor) +}