File size: 6,365 Bytes
2409829 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
use clap::{Args, Parser, Subcommand};
use fern::colors::{Color, ColoredLevelConfig};
use futures::executor::block_on;
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::text::FontCache;
use graphene_std::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::DynamicExecutor;
use interpreted_executor::util::wrap_network_in_scope;
use std::error::Error;
use std::path::PathBuf;
use std::sync::Arc;
struct UpdateLogger {}
impl NodeGraphUpdateSender for UpdateLogger {
fn send(&self, message: NodeGraphUpdateMessage) {
println!("{message:?}");
}
}
#[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<PathBuf>,
/// 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<dyn Error>> {
let app = App::parse();
let log_level = app.global_opts.verbose;
init_logging(log_level);
let document_path = match app.command {
Command::Compile { ref document, .. } => document,
Command::Run { ref document, .. } => document,
};
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 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();
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(preferences),
});
let proto_graph = compile_graph(document_string, editor_api)?;
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 = 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(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("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
))
})
.apply()
.unwrap();
}
// Migrations are done in the editor which is unfortunately not available here.
// TODO: remove this and share migrations between the editor and the CLI.
fn fix_nodes(network: &mut NodeNetwork) {
for node in network.nodes.values_mut() {
match &mut node.implementation {
// Recursively fix
DocumentNodeImplementation::Network(network) => fix_nodes(network),
// This replicates the migration from the editor linked:
// https://github.com/GraphiteEditor/Graphite/blob/d68f91ccca69e90e6d2df78d544d36cd1aaf348e/editor/src/messages/portfolio/portfolio_message_handler.rs#L535
// Since the CLI doesn't have the document node definitions, a less robust method of just patching the inputs is used.
DocumentNodeImplementation::ProtoNode(proto_node_identifier)
if (proto_node_identifier.name.starts_with("graphene_core::ConstructLayerNode") || proto_node_identifier.name.starts_with("graphene_core::AddArtboardNode"))
&& node.inputs.len() < 3 =>
{
node.inputs.push(NodeInput::Reflection(DocumentNodeMetadata::DocumentNodePath));
}
_ => {}
}
}
}
fn compile_graph(document_string: String, editor_api: Arc<WasmEditorApi>) -> Result<ProtoNetwork, Box<dyn Error>> {
let mut network = load_network(&document_string);
fix_nodes(&mut network);
let substitutions = preprocessor::generate_node_substitutions();
preprocessor::expand_network(&mut network, &substitutions);
let wrapped_network = wrap_network_in_scope(network.clone(), editor_api);
let compiler = Compiler {};
compiler.compile_single(wrapped_network).map_err(|x| x.into())
}
fn create_executor(proto_network: ProtoNetwork) -> Result<DynamicExecutor, Box<dyn Error>> {
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)
}
|