mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Add node macro support for injecting nodes into global scope (#4221)
* Macro support * Preprocessor support * Allow Cow str as scope name * Use Passthrough node to work around compiler auto wrapping the producer node and invalidating the node id * Stable NodeIds * Less hacky but with bad performace * Remove Cow from RegistryValueSource::Scope * Connect scope injections to the inner network export --------- Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
@@ -43,7 +43,7 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
|
|||||||
description,
|
description,
|
||||||
properties,
|
properties,
|
||||||
context_features,
|
context_features,
|
||||||
memoize: _,
|
..
|
||||||
} = metadata;
|
} = metadata;
|
||||||
|
|
||||||
let implementations = node_registry.get(id).unwrap_or(&empty_implementations);
|
let implementations = node_registry.get(id).unwrap_or(&empty_implementations);
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
|||||||
use glam::{DAffine2, DVec2, UVec2};
|
use glam::{DAffine2, DVec2, UVec2};
|
||||||
use graph_craft::application_io::resource::ResourceRegistry;
|
use graph_craft::application_io::resource::ResourceRegistry;
|
||||||
use graph_craft::application_io::{PlatformApplicationIo, PlatformEditorApi};
|
use graph_craft::application_io::{PlatformApplicationIo, PlatformEditorApi};
|
||||||
|
use graph_craft::concrete;
|
||||||
use graph_craft::document::value::{RenderOutput, RenderOutputType, TaggedValue};
|
use graph_craft::document::value::{RenderOutput, RenderOutputType, TaggedValue};
|
||||||
use graph_craft::document::{NodeId, NodeNetwork};
|
use graph_craft::document::{NodeId, NodeNetwork};
|
||||||
use graph_craft::graphene_compiler::Compiler;
|
use graph_craft::graphene_compiler::Compiler;
|
||||||
use graph_craft::proto::GraphErrors;
|
use graph_craft::proto::GraphErrors;
|
||||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
|
||||||
use graphene_std::application_io::{ApplicationIo, ExportFormat, ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
use graphene_std::application_io::{ApplicationIo, ExportFormat, ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||||
use graphene_std::bounds::{BoundingBox, RenderBoundingBox};
|
use graphene_std::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
use graphene_std::list::List;
|
use graphene_std::list::List;
|
||||||
@@ -49,8 +49,7 @@ pub struct NodeRuntime {
|
|||||||
/// Which node is inspected and which monitor node is used (if any) for the current execution.
|
/// Which node is inspected and which monitor node is used (if any) for the current execution.
|
||||||
inspect_state: Option<InspectState>,
|
inspect_state: Option<InspectState>,
|
||||||
|
|
||||||
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
|
preprocessor: preprocessor::Preprocessor,
|
||||||
substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
|
|
||||||
|
|
||||||
// TODO: Remove, it doesn't need to be persisted anymore
|
// TODO: Remove, it doesn't need to be persisted anymore
|
||||||
/// The current renders of the thumbnails for layer nodes.
|
/// The current renders of the thumbnails for layer nodes.
|
||||||
@@ -146,7 +145,7 @@ impl NodeRuntime {
|
|||||||
node_graph_errors: Vec::new(),
|
node_graph_errors: Vec::new(),
|
||||||
monitor_nodes: Vec::new(),
|
monitor_nodes: Vec::new(),
|
||||||
|
|
||||||
substitutions: preprocessor::generate_node_substitutions(),
|
preprocessor: preprocessor::Preprocessor::new(),
|
||||||
|
|
||||||
thumbnail_renders: Default::default(),
|
thumbnail_renders: Default::default(),
|
||||||
vector_modify: Default::default(),
|
vector_modify: Default::default(),
|
||||||
@@ -347,7 +346,7 @@ impl NodeRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, String)> {
|
async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, String)> {
|
||||||
if let Err(e) = preprocessor::expand_network(&mut graph, &self.substitutions, &self.resources) {
|
if let Err(e) = self.preprocessor.expand_network(&mut graph, &self.resources) {
|
||||||
return Err((ResolvedDocumentNodeTypesDelta::default(), e.to_string()));
|
return Err((ResolvedDocumentNodeTypesDelta::default(), e.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -731,9 +731,9 @@ impl NodeNetwork {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Replace all references in any node of `old_output` with `new_output`
|
/// Replace all references in any node of `old_output` with `new_output`
|
||||||
fn replace_network_outputs(&mut self, old_output: NodeInput, new_output: NodeInput) {
|
fn replace_network_outputs(&mut self, old_output: &NodeInput, new_output: &NodeInput) {
|
||||||
for output in self.exports.iter_mut() {
|
for output in self.exports.iter_mut() {
|
||||||
if *output == old_output {
|
if *output == *old_output {
|
||||||
*output = new_output.clone();
|
*output = new_output.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -899,27 +899,44 @@ impl NodeNetwork {
|
|||||||
}
|
}
|
||||||
// TODO: Add support for flattening exports that are NodeInput::Import (https://github.com/GraphiteEditor/Graphite/issues/1762)
|
// TODO: Add support for flattening exports that are NodeInput::Import (https://github.com/GraphiteEditor/Graphite/issues/1762)
|
||||||
|
|
||||||
|
self.replace_node_with_its_exports(id, &node.original_location, &inner_network.exports);
|
||||||
|
|
||||||
|
for node_id in new_nodes {
|
||||||
|
self.flatten_with_fns(node_id, map_ids, gen_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_node_with_its_exports(&mut self, id: NodeId, original_location: &OriginalLocation, exports: &[NodeInput]) {
|
||||||
|
// Connect scope injections to the inner network export
|
||||||
|
self.scope_injections.values_mut().for_each(|(node_id, _ty)| {
|
||||||
|
if node_id == &id {
|
||||||
|
let Some(export) = exports.first() else {
|
||||||
|
log::error!("Inner network should have at least one export");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let NodeInput::Node { node_id: export_id, output_index: _ } = export {
|
||||||
|
*node_id = *export_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Connect all nodes that were previously connected to this node to the nodes of the inner network
|
// Connect all nodes that were previously connected to this node to the nodes of the inner network
|
||||||
for (i, export) in inner_network.exports.into_iter().enumerate() {
|
for (i, export) in exports.iter().enumerate() {
|
||||||
if let NodeInput::Node { node_id, output_index, .. } = &export {
|
if let NodeInput::Node { node_id, output_index, .. } = &export {
|
||||||
for deps in &node.original_location.dependants {
|
for deps in &original_location.dependants {
|
||||||
for dep in deps {
|
for dep in deps {
|
||||||
self.replace_node_inputs(*dep, (id, i), (*node_id, *output_index));
|
self.replace_node_inputs(*dep, (id, i), (*node_id, *output_index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(new_output_node) = self.nodes.get_mut(node_id) {
|
if let Some(new_output_node) = self.nodes.get_mut(node_id) {
|
||||||
for dep in &node.original_location.dependants[i] {
|
for dep in &original_location.dependants[i] {
|
||||||
new_output_node.original_location.dependants[*output_index].push(*dep);
|
new_output_node.original_location.dependants[*output_index].push(*dep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.replace_network_outputs(NodeInput::node(id, i), export);
|
self.replace_network_outputs(&NodeInput::node(id, i), export);
|
||||||
}
|
|
||||||
|
|
||||||
for node_id in new_nodes {
|
|
||||||
self.flatten_with_fns(node_id, map_ids, gen_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,8 +242,8 @@ fn compile_graph(document_string: String, editor_api: Arc<PlatformEditorApi>) ->
|
|||||||
let mut network = load_network(&document_string);
|
let mut network = load_network(&document_string);
|
||||||
fix_nodes(&mut network);
|
fix_nodes(&mut network);
|
||||||
|
|
||||||
let substitutions = preprocessor::generate_node_substitutions();
|
let preprocessor = preprocessor::Preprocessor::new();
|
||||||
preprocessor::expand_network(&mut network, &substitutions, &ResourceRegistry::default()).expect("Failed to expand network"); // TODO: actually load the resources from the document
|
preprocessor.expand_network(&mut network, &ResourceRegistry::default()).expect("Failed to expand network"); // TODO: actually load the resources from the document
|
||||||
|
|
||||||
let wrapped_network = wrap_network_in_scope(network.clone(), editor_api);
|
let wrapped_network = wrap_network_in_scope(network.clone(), editor_api);
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use interpreted_executor::util::wrap_network_in_scope;
|
|||||||
pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) {
|
pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) {
|
||||||
let mut network = load_from_name(name);
|
let mut network = load_from_name(name);
|
||||||
let editor_api = std::sync::Arc::new(EditorApi::default());
|
let editor_api = std::sync::Arc::new(EditorApi::default());
|
||||||
let substitutions = preprocessor::generate_node_substitutions();
|
let preprocessor = preprocessor::Preprocessor::new();
|
||||||
preprocessor::expand_network(&mut network, &substitutions, &ResourceRegistry::default()).unwrap();
|
preprocessor.expand_network(&mut network, &ResourceRegistry::default()).unwrap();
|
||||||
let network = wrap_network_in_scope(network, editor_api);
|
let network = wrap_network_in_scope(network, editor_api);
|
||||||
let proto_network = compile(network);
|
let proto_network = compile(network);
|
||||||
let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap();
|
let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap();
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub struct NodeMetadata {
|
|||||||
pub properties: Option<&'static str>,
|
pub properties: Option<&'static str>,
|
||||||
pub context_features: Vec<ContextFeature>,
|
pub context_features: Vec<ContextFeature>,
|
||||||
pub memoize: bool,
|
pub memoize: bool,
|
||||||
|
pub inject_scope: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translation struct between macro and definition
|
// Translation struct between macro and definition
|
||||||
|
|||||||
@@ -154,6 +154,13 @@ impl ProtoNodeIdentifier {
|
|||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
self.name.as_ref()
|
self.name.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn as_static_str(&self) -> &'static str {
|
||||||
|
match self.name {
|
||||||
|
Cow::Borrowed(name) => name,
|
||||||
|
Cow::Owned(_) => panic!("`as_static_str` called on a `ProtoNodeIdentifier` backed by an owned string"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ProtoNodeIdentifier {
|
impl Display for ProtoNodeIdentifier {
|
||||||
|
|||||||
@@ -172,7 +172,13 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
quote!(RegistryValueSource::Default(stringify!(#data)))
|
quote!(RegistryValueSource::Default(stringify!(#data)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParsedValueSource::Scope(data) => quote!(RegistryValueSource::Scope(#data)),
|
ParsedValueSource::Scope(data) => {
|
||||||
|
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(_), .. }) = data {
|
||||||
|
quote!(RegistryValueSource::Scope(#data))
|
||||||
|
} else {
|
||||||
|
quote!(RegistryValueSource::Scope(#data.as_static_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => quote!(RegistryValueSource::None),
|
_ => quote!(RegistryValueSource::None),
|
||||||
},
|
},
|
||||||
_ => quote!(RegistryValueSource::None),
|
_ => quote!(RegistryValueSource::None),
|
||||||
@@ -402,6 +408,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
|
|
||||||
let properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));
|
let properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));
|
||||||
let memoize_flag = attributes.memoize;
|
let memoize_flag = attributes.memoize;
|
||||||
|
let inject_scope_flag = attributes.inject_scope;
|
||||||
|
|
||||||
let cfg = crate::shader_nodes::modify_cfg(attributes);
|
let cfg = crate::shader_nodes::modify_cfg(attributes);
|
||||||
let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, core_types, &identifier, &cfg);
|
let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, core_types, &identifier, &cfg);
|
||||||
@@ -500,6 +507,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
properties: #properties,
|
properties: #properties,
|
||||||
context_features: vec![#(ContextFeature::#context_features,)*],
|
context_features: vec![#(ContextFeature::#context_features,)*],
|
||||||
memoize: #memoize_flag,
|
memoize: #memoize_flag,
|
||||||
|
inject_scope: #inject_scope_flag,
|
||||||
fields: vec![
|
fields: vec![
|
||||||
#(
|
#(
|
||||||
FieldMetadata {
|
FieldMetadata {
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ pub(crate) struct NodeFnAttributes {
|
|||||||
pub(crate) serialize: Option<Path>,
|
pub(crate) serialize: Option<Path>,
|
||||||
/// Whether the preprocessor should add a Memoize node after this node in the generated subnetwork
|
/// Whether the preprocessor should add a Memoize node after this node in the generated subnetwork
|
||||||
pub(crate) memoize: bool,
|
pub(crate) memoize: bool,
|
||||||
|
/// Whether this node provides a scope
|
||||||
|
pub(crate) inject_scope: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@@ -61,7 +63,7 @@ pub enum ParsedValueSource {
|
|||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
Default(TokenStream2),
|
Default(TokenStream2),
|
||||||
Scope(LitStr),
|
Scope(Expr),
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[widget(ParsedWidgetOverride::Hidden)]
|
// #[widget(ParsedWidgetOverride::Hidden)]
|
||||||
@@ -261,6 +263,7 @@ impl Parse for NodeFnAttributes {
|
|||||||
let mut shader_node = None;
|
let mut shader_node = None;
|
||||||
let mut serialize = None;
|
let mut serialize = None;
|
||||||
let mut memoize = false;
|
let mut memoize = false;
|
||||||
|
let mut inject_scope = false;
|
||||||
|
|
||||||
let content = input;
|
let content = input;
|
||||||
// let content;
|
// let content;
|
||||||
@@ -391,13 +394,25 @@ impl Parse for NodeFnAttributes {
|
|||||||
}
|
}
|
||||||
memoize = true;
|
memoize = true;
|
||||||
}
|
}
|
||||||
|
// Instructs the preprocessor to make this node available as a scope.
|
||||||
|
// Other nodes can then access it with `#[scope(node::IDENTIFIER)]`.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
// #[node_macro::node(..., inject_scope, ...)]
|
||||||
|
"inject_scope" => {
|
||||||
|
let path = meta.require_path_only()?;
|
||||||
|
if inject_scope {
|
||||||
|
return Err(Error::new_spanned(path, "Multiple 'inject_scope' attributes are not allowed"));
|
||||||
|
}
|
||||||
|
inject_scope = true;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::new_spanned(
|
return Err(Error::new_spanned(
|
||||||
meta,
|
meta,
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Unsupported attribute in `node`.
|
Unsupported attribute in `node`.
|
||||||
Supported attributes are 'category', 'name', 'path', 'skip_impl', 'properties', 'cfg', 'shader_node', 'serialize', and 'memoize'.
|
Supported attributes are 'category', 'name', 'path', 'skip_impl', 'properties', 'cfg', 'shader_node', 'serialize', 'memoize', and 'inject_scope'.
|
||||||
Example usage:
|
Example usage:
|
||||||
#[node_macro::node(..., name("Test Node"), ...)]
|
#[node_macro::node(..., name("Test Node"), ...)]
|
||||||
"#
|
"#
|
||||||
@@ -430,6 +445,7 @@ impl Parse for NodeFnAttributes {
|
|||||||
shader_node,
|
shader_node,
|
||||||
serialize,
|
serialize,
|
||||||
memoize,
|
memoize,
|
||||||
|
inject_scope,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -979,7 +995,7 @@ mod tests {
|
|||||||
assert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string());
|
assert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string());
|
||||||
}
|
}
|
||||||
(ParsedValueSource::Scope(p), ParsedValueSource::Scope(e)) => {
|
(ParsedValueSource::Scope(p), ParsedValueSource::Scope(e)) => {
|
||||||
assert_eq!(p.value(), e.value());
|
assert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string());
|
||||||
}
|
}
|
||||||
_ => panic!("Mismatched default values"),
|
_ => panic!("Mismatched default values"),
|
||||||
}
|
}
|
||||||
@@ -1038,6 +1054,7 @@ mod tests {
|
|||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
memoize: false,
|
memoize: false,
|
||||||
|
inject_scope: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("add", Span::call_site()),
|
fn_name: Ident::new("add", Span::call_site()),
|
||||||
struct_name: Ident::new("Add", Span::call_site()),
|
struct_name: Ident::new("Add", Span::call_site()),
|
||||||
@@ -1107,6 +1124,7 @@ mod tests {
|
|||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
memoize: false,
|
memoize: false,
|
||||||
|
inject_scope: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("transform", Span::call_site()),
|
fn_name: Ident::new("transform", Span::call_site()),
|
||||||
struct_name: Ident::new("Transform", Span::call_site()),
|
struct_name: Ident::new("Transform", Span::call_site()),
|
||||||
@@ -1190,6 +1208,7 @@ mod tests {
|
|||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
memoize: false,
|
memoize: false,
|
||||||
|
inject_scope: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("circle", Span::call_site()),
|
fn_name: Ident::new("circle", Span::call_site()),
|
||||||
struct_name: Ident::new("Circle", Span::call_site()),
|
struct_name: Ident::new("Circle", Span::call_site()),
|
||||||
@@ -1255,6 +1274,7 @@ mod tests {
|
|||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
memoize: false,
|
memoize: false,
|
||||||
|
inject_scope: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("levels", Span::call_site()),
|
fn_name: Ident::new("levels", Span::call_site()),
|
||||||
struct_name: Ident::new("Levels", Span::call_site()),
|
struct_name: Ident::new("Levels", Span::call_site()),
|
||||||
@@ -1332,6 +1352,7 @@ mod tests {
|
|||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
memoize: false,
|
memoize: false,
|
||||||
|
inject_scope: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("add", Span::call_site()),
|
fn_name: Ident::new("add", Span::call_site()),
|
||||||
struct_name: Ident::new("Add", Span::call_site()),
|
struct_name: Ident::new("Add", Span::call_site()),
|
||||||
@@ -1397,6 +1418,7 @@ mod tests {
|
|||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
memoize: false,
|
memoize: false,
|
||||||
|
inject_scope: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("load_image", Span::call_site()),
|
fn_name: Ident::new("load_image", Span::call_site()),
|
||||||
struct_name: Ident::new("LoadImage", Span::call_site()),
|
struct_name: Ident::new("LoadImage", Span::call_site()),
|
||||||
@@ -1462,6 +1484,7 @@ mod tests {
|
|||||||
shader_node: None,
|
shader_node: None,
|
||||||
serialize: None,
|
serialize: None,
|
||||||
memoize: false,
|
memoize: false,
|
||||||
|
inject_scope: false,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("custom_node", Span::call_site()),
|
fn_name: Ident::new("custom_node", Span::call_site()),
|
||||||
struct_name: Ident::new("CustomNode", Span::call_site()),
|
struct_name: Ident::new("CustomNode", Span::call_site()),
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ impl PerPixelAdjustCodegen<'_> {
|
|||||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||||
ty: parse_quote!(&'a WgpuExecutor),
|
ty: parse_quote!(&'a WgpuExecutor),
|
||||||
exposed: true,
|
exposed: true,
|
||||||
value_source: ParsedValueSource::Scope(LitStr::new("wgpu-executor", Span::call_site())),
|
value_source: ParsedValueSource::Scope(parse_quote!("wgpu-executor")),
|
||||||
number_soft_min: None,
|
number_soft_min: None,
|
||||||
number_soft_max: None,
|
number_soft_max: None,
|
||||||
number_hard_min: None,
|
number_hard_min: None,
|
||||||
|
|||||||
@@ -34,3 +34,13 @@ fn unwrap_option<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<
|
|||||||
fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&List<Raster<CPU>>)] value: &'i T) -> T {
|
fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&List<Raster<CPU>>)] value: &'i T) -> T {
|
||||||
value.clone()
|
value.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Debug"), inject_scope)]
|
||||||
|
fn inject_scope_test_producer(_: impl Ctx) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Debug"))]
|
||||||
|
fn inject_scope_test_consumer(_: impl Ctx, _primary: (), #[scope(inject_scope_test_producer::IDENTIFIER)] injected: bool) -> bool {
|
||||||
|
injected
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
|
use graph_craft::Type;
|
||||||
use graph_craft::application_io::resource::{ResourceId, ResourceRegistry};
|
use graph_craft::application_io::resource::{ResourceId, ResourceRegistry};
|
||||||
use graph_craft::document::value::*;
|
use graph_craft::document::value::*;
|
||||||
use graph_craft::document::*;
|
use graph_craft::document::*;
|
||||||
@@ -9,11 +10,21 @@ use graph_craft::{ProtoNodeIdentifier, concrete};
|
|||||||
use graphene_std::registry::*;
|
use graphene_std::registry::*;
|
||||||
use graphene_std::*;
|
use graphene_std::*;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
|
|
||||||
pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<ProtoNodeIdentifier, DocumentNode>, resources: &ResourceRegistry) -> Result<(), PreprocessorError> {
|
#[derive(Debug, Default, Clone)]
|
||||||
replace_resource_inputs(network, resources)?;
|
pub struct Preprocessor {
|
||||||
expand_network_inner(network, substitutions);
|
substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
|
||||||
Ok(())
|
inject_scopes: HashMap<ProtoNodeIdentifier, (DocumentNode, Type)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Preprocessor {
|
||||||
|
pub fn expand_network(&self, network: &mut NodeNetwork, resources: &ResourceRegistry) -> Result<(), PreprocessorError> {
|
||||||
|
self.insert_inject_scopes(network);
|
||||||
|
replace_resource_inputs(network, resources)?;
|
||||||
|
self.expand_network_inner(network);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace every `TaggedValue::Resource(hash)` input with a reference to a freshly inserted `resource` proto node.
|
/// Replace every `TaggedValue::Resource(hash)` input with a reference to a freshly inserted `resource` proto node.
|
||||||
@@ -61,160 +72,191 @@ fn replace_resource_inputs(network: &mut NodeNetwork, resources: &ResourceRegist
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_network_inner(network: &mut NodeNetwork, substitutions: &HashMap<ProtoNodeIdentifier, DocumentNode>) {
|
impl Preprocessor {
|
||||||
if network.generated {
|
fn insert_inject_scopes(&self, network: &mut NodeNetwork) {
|
||||||
return;
|
for (identifier, (template, ty)) in self.inject_scopes.iter() {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
|
||||||
|
identifier.as_str().hash(&mut hasher);
|
||||||
|
let producer_id = NodeId(hasher.finish());
|
||||||
|
network.nodes.insert(producer_id, template.clone());
|
||||||
|
|
||||||
|
network.scope_injections.insert(identifier.as_str().to_string(), (producer_id, ty.clone()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for node in network.nodes.values_mut() {
|
fn expand_network_inner(&self, network: &mut NodeNetwork) {
|
||||||
match &mut node.implementation {
|
if network.generated {
|
||||||
DocumentNodeImplementation::Network(node_network) => expand_network_inner(node_network, substitutions),
|
return;
|
||||||
DocumentNodeImplementation::ProtoNode(proto_node_identifier) => {
|
}
|
||||||
if let Some(new_node) = substitutions.get(proto_node_identifier) {
|
|
||||||
// Reconcile the document node's inputs with what the current node definition expects,
|
|
||||||
// since the saved document may have fewer or more inputs than the current version
|
|
||||||
while node.inputs.len() < new_node.inputs.len() {
|
|
||||||
node.inputs.push(new_node.inputs[node.inputs.len()].clone());
|
|
||||||
}
|
|
||||||
node.inputs.truncate(new_node.inputs.len());
|
|
||||||
|
|
||||||
node.implementation = new_node.implementation.clone();
|
for node in network.nodes.values_mut() {
|
||||||
|
match &mut node.implementation {
|
||||||
|
DocumentNodeImplementation::Network(node_network) => self.expand_network_inner(node_network),
|
||||||
|
DocumentNodeImplementation::ProtoNode(proto_node_identifier) => {
|
||||||
|
if let Some(new_node) = self.substitutions.get(proto_node_identifier) {
|
||||||
|
// Reconcile the document node's inputs with what the current node definition expects,
|
||||||
|
// since the saved document may have fewer or more inputs than the current version
|
||||||
|
while node.inputs.len() < new_node.inputs.len() {
|
||||||
|
node.inputs.push(new_node.inputs[node.inputs.len()].clone());
|
||||||
|
}
|
||||||
|
node.inputs.truncate(new_node.inputs.len());
|
||||||
|
|
||||||
|
node.implementation = new_node.implementation.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DocumentNodeImplementation::Extract => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut substitutions = HashMap::new();
|
||||||
|
let mut inject_scopes = HashMap::new();
|
||||||
|
// We pre initialize the node registry here to avoid a deadlock
|
||||||
|
let into_node_registry = &*interpreted_executor::node_registry::NODE_REGISTRY;
|
||||||
|
let node_registry = core_types::registry::NODE_REGISTRY.lock().unwrap();
|
||||||
|
for (id, metadata) in core_types::registry::NODE_METADATA.lock().unwrap().iter() {
|
||||||
|
let id = id.clone();
|
||||||
|
|
||||||
|
let NodeMetadata { fields, memoize, inject_scope, .. } = metadata;
|
||||||
|
let Some(implementations) = node_registry.get(&id) else { continue };
|
||||||
|
let valid_call_args: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();
|
||||||
|
let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });
|
||||||
|
let mut node_io_types = vec![HashSet::new(); fields.len()];
|
||||||
|
for (_, node_io) in implementations.iter() {
|
||||||
|
for (i, ty) in node_io.inputs.iter().enumerate() {
|
||||||
|
node_io_types[i].insert(ty.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentNodeImplementation::Extract => (),
|
let mut input_type = &first_node_io.call_argument;
|
||||||
}
|
if valid_call_args.len() > 1 {
|
||||||
}
|
input_type = &const { generic!(D) };
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNode> {
|
|
||||||
let mut custom = HashMap::new();
|
|
||||||
// We pre initialize the node registry here to avoid a deadlock
|
|
||||||
let into_node_registry = &*interpreted_executor::node_registry::NODE_REGISTRY;
|
|
||||||
let node_registry = core_types::registry::NODE_REGISTRY.lock().unwrap();
|
|
||||||
for (id, metadata) in core_types::registry::NODE_METADATA.lock().unwrap().iter() {
|
|
||||||
let id = id.clone();
|
|
||||||
|
|
||||||
let NodeMetadata { fields, memoize, .. } = metadata;
|
|
||||||
let Some(implementations) = node_registry.get(&id) else { continue };
|
|
||||||
let valid_call_args: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();
|
|
||||||
let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });
|
|
||||||
let mut node_io_types = vec![HashSet::new(); fields.len()];
|
|
||||||
for (_, node_io) in implementations.iter() {
|
|
||||||
for (i, ty) in node_io.inputs.iter().enumerate() {
|
|
||||||
node_io_types[i].insert(ty.clone());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
let mut input_type = &first_node_io.call_argument;
|
|
||||||
if valid_call_args.len() > 1 {
|
|
||||||
input_type = &const { generic!(D) };
|
|
||||||
}
|
|
||||||
|
|
||||||
let inputs: Vec<_> = node_inputs(fields, first_node_io);
|
let inputs: Vec<_> = node_inputs(fields, first_node_io);
|
||||||
let input_count = inputs.len();
|
let input_count = inputs.len();
|
||||||
let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect();
|
let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect();
|
||||||
|
|
||||||
let passthrough_node = ops::passthrough::IDENTIFIER;
|
let passthrough_node = ops::passthrough::IDENTIFIER;
|
||||||
|
|
||||||
let mut generated_nodes = 0;
|
let mut generated_nodes = 0;
|
||||||
let mut nodes: HashMap<_, _, _> = node_io_types
|
let mut nodes: HashMap<_, _, _> = node_io_types
|
||||||
.iter()
|
.iter()
|
||||||
.take(input_count)
|
.take(input_count)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, inputs)| {
|
.map(|(i, inputs)| {
|
||||||
(
|
(
|
||||||
NodeId(i as u64),
|
NodeId(i as u64),
|
||||||
match inputs.len() {
|
match inputs.len() {
|
||||||
1 => {
|
1 => {
|
||||||
let input = inputs.iter().next().unwrap();
|
let input = inputs.iter().next().unwrap();
|
||||||
let input_ty = input.nested_type();
|
let input_ty = input.nested_type();
|
||||||
let mut inputs = vec![NodeInput::import(input.clone(), i)];
|
let mut inputs = vec![NodeInput::import(input.clone(), i)];
|
||||||
|
|
||||||
let into_node_identifier = ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::IntoNode<{}>", input_ty.identifier_name()));
|
let into_node_identifier = ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::IntoNode<{}>", input_ty.identifier_name()));
|
||||||
let convert_node_identifier = ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::ConvertNode<{}>", input_ty.identifier_name()));
|
let convert_node_identifier = ProtoNodeIdentifier::with_owned_string(format!("graphene_core::ops::ConvertNode<{}>", input_ty.identifier_name()));
|
||||||
|
|
||||||
let proto_node = if into_node_registry.keys().any(|ident: &ProtoNodeIdentifier| ident.as_str() == into_node_identifier.as_str()) {
|
let proto_node = if into_node_registry.keys().any(|ident: &ProtoNodeIdentifier| ident.as_str() == into_node_identifier.as_str()) {
|
||||||
generated_nodes += 1;
|
generated_nodes += 1;
|
||||||
into_node_identifier
|
into_node_identifier
|
||||||
} else if into_node_registry.keys().any(|ident| ident.as_str() == convert_node_identifier.as_str()) {
|
} else if into_node_registry.keys().any(|ident| ident.as_str() == convert_node_identifier.as_str()) {
|
||||||
generated_nodes += 1;
|
generated_nodes += 1;
|
||||||
inputs.push(NodeInput::value(TaggedValue::None, false));
|
inputs.push(NodeInput::value(TaggedValue::None, false));
|
||||||
convert_node_identifier
|
convert_node_identifier
|
||||||
} else {
|
} else {
|
||||||
passthrough_node.clone()
|
passthrough_node.clone()
|
||||||
};
|
};
|
||||||
let mut original_location = OriginalLocation::default();
|
let mut original_location = OriginalLocation::default();
|
||||||
original_location.auto_convert_index = Some(i);
|
original_location.auto_convert_index = Some(i);
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs,
|
inputs,
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(proto_node),
|
implementation: DocumentNodeImplementation::ProtoNode(proto_node),
|
||||||
visible: true,
|
visible: true,
|
||||||
original_location,
|
original_location,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
_ => DocumentNode {
|
||||||
_ => DocumentNode {
|
inputs: vec![NodeInput::import(generic!(X), i)],
|
||||||
inputs: vec![NodeInput::import(generic!(X), i)],
|
implementation: DocumentNodeImplementation::ProtoNode(passthrough_node.clone()),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(passthrough_node.clone()),
|
visible: false,
|
||||||
visible: false,
|
..Default::default()
|
||||||
..Default::default()
|
},
|
||||||
},
|
},
|
||||||
},
|
)
|
||||||
)
|
})
|
||||||
})
|
.collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
if generated_nodes == 0 && !memoize {
|
if generated_nodes == 0 && !memoize && !inject_scope {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let document_node = DocumentNode {
|
let document_node = DocumentNode {
|
||||||
inputs: network_inputs,
|
inputs: network_inputs,
|
||||||
call_argument: input_type.clone(),
|
call_argument: input_type.clone(),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
|
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
|
||||||
visible: true,
|
|
||||||
skip_deduplication: false,
|
|
||||||
context_features: ContextDependencies::from(metadata.context_features.as_slice()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
nodes.insert(NodeId(input_count as u64), document_node);
|
|
||||||
|
|
||||||
// If memoize is requested, append a Memoize node after the main node and redirect the export through it
|
|
||||||
let export_node_id = if *memoize {
|
|
||||||
let memoize_node_id = NodeId(input_count as u64 + 1);
|
|
||||||
let memoize_node = DocumentNode {
|
|
||||||
inputs: vec![NodeInput::node(NodeId(input_count as u64), 0)],
|
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memoize::IDENTIFIER.clone()),
|
|
||||||
visible: true,
|
visible: true,
|
||||||
|
skip_deduplication: false,
|
||||||
|
context_features: ContextDependencies::from(metadata.context_features.as_slice()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
nodes.insert(memoize_node_id, memoize_node);
|
|
||||||
memoize_node_id
|
|
||||||
} else {
|
|
||||||
NodeId(input_count as u64)
|
|
||||||
};
|
|
||||||
|
|
||||||
let node = DocumentNode {
|
nodes.insert(NodeId(input_count as u64), document_node);
|
||||||
inputs,
|
|
||||||
call_argument: input_type.clone(),
|
|
||||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
|
||||||
exports: vec![NodeInput::Node {
|
|
||||||
node_id: export_node_id,
|
|
||||||
output_index: 0,
|
|
||||||
}],
|
|
||||||
nodes,
|
|
||||||
scope_injections: Default::default(),
|
|
||||||
generated: true,
|
|
||||||
}),
|
|
||||||
visible: true,
|
|
||||||
skip_deduplication: false,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
custom.insert(id.clone(), node);
|
// If memoize is requested, append a Memoize node after the main node and redirect the export through it
|
||||||
|
let export_node_id = if *memoize {
|
||||||
|
let memoize_node_id = NodeId(input_count as u64 + 1);
|
||||||
|
let memoize_node = DocumentNode {
|
||||||
|
inputs: vec![NodeInput::node(NodeId(input_count as u64), 0)],
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memoize::IDENTIFIER.clone()),
|
||||||
|
visible: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
nodes.insert(memoize_node_id, memoize_node);
|
||||||
|
memoize_node_id
|
||||||
|
} else {
|
||||||
|
NodeId(input_count as u64)
|
||||||
|
};
|
||||||
|
|
||||||
|
let node = DocumentNode {
|
||||||
|
inputs,
|
||||||
|
call_argument: input_type.clone(),
|
||||||
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
|
exports: vec![NodeInput::Node {
|
||||||
|
node_id: export_node_id,
|
||||||
|
output_index: 0,
|
||||||
|
}],
|
||||||
|
nodes,
|
||||||
|
scope_injections: Default::default(),
|
||||||
|
generated: true,
|
||||||
|
}),
|
||||||
|
visible: true,
|
||||||
|
skip_deduplication: false,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
substitutions.insert(id.clone(), node);
|
||||||
|
|
||||||
|
// If `inject_scope` is requested, prepare the proto node template and type info needed
|
||||||
|
if *inject_scope
|
||||||
|
&& let Some(implementations) = node_registry.get(&id)
|
||||||
|
&& let Some((_, node_io)) = implementations.first()
|
||||||
|
{
|
||||||
|
let template = DocumentNode {
|
||||||
|
inputs: node_inputs(fields, node_io),
|
||||||
|
call_argument: node_io.call_argument.clone(),
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
|
||||||
|
visible: true,
|
||||||
|
context_features: ContextDependencies::from(metadata.context_features.as_slice()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
inject_scopes.insert(id.clone(), (template, node_io.return_value.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { substitutions, inject_scopes }
|
||||||
}
|
}
|
||||||
|
|
||||||
custom
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTypes) -> Vec<NodeInput> {
|
pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTypes) -> Vec<NodeInput> {
|
||||||
@@ -229,7 +271,7 @@ pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTyp
|
|||||||
};
|
};
|
||||||
let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed };
|
let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed };
|
||||||
|
|
||||||
match field.value_source {
|
match &field.value_source {
|
||||||
RegistryValueSource::None => {}
|
RegistryValueSource::None => {}
|
||||||
RegistryValueSource::Default(data) => {
|
RegistryValueSource::Default(data) => {
|
||||||
if let Some(custom_default) = TaggedValue::from_primitive_string(data, ty) {
|
if let Some(custom_default) = TaggedValue::from_primitive_string(data, ty) {
|
||||||
@@ -239,7 +281,7 @@ pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTyp
|
|||||||
warn!("Failed to parse default value for type `{ty:?}` with data `{data}`");
|
warn!("Failed to parse default value for type `{ty:?}` with data `{data}`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)),
|
RegistryValueSource::Scope(data) => return NodeInput::scope(*data),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(type_default) = TaggedValue::from_type(ty) {
|
if let Some(type_default) = TaggedValue::from_type(ty) {
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ fn write_inputs(page: &mut std::fs::File, valid_input_types: &[Vec<core_types::T
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Details: default value
|
// Details: default value
|
||||||
let default_value = match field.value_source {
|
let default_value = match &field.value_source {
|
||||||
RegistryValueSource::Default(default_value) => Some(default_value.to_string().replace(" :: ", "::")),
|
RegistryValueSource::Default(default_value) => Some(default_value.to_string().replace(" :: ", "::")),
|
||||||
_ => field
|
_ => field
|
||||||
.default_type
|
.default_type
|
||||||
|
|||||||
Reference in New Issue
Block a user