Add comments to help explain Graphene concepts
This commit is contained in:
parent
ee08938bb0
commit
bafde43145
|
|
@ -93,6 +93,7 @@ impl NodeImplementation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Acts as a description for a [DocumentNode] before it gets instantiated as one.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DocumentNodeType {
|
pub struct DocumentNodeType {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
|
@ -125,6 +126,8 @@ impl Default for DocumentNodeType {
|
||||||
static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<Vec<DocumentNodeType>> = once_cell::sync::Lazy::new(static_nodes);
|
static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<Vec<DocumentNodeType>> = once_cell::sync::Lazy::new(static_nodes);
|
||||||
|
|
||||||
// TODO: Dynamic node library
|
// TODO: Dynamic node library
|
||||||
|
/// Defines the "signature" or "header file"-like metadata for the document nodes, but not the implementation (which is defined in the node registry).
|
||||||
|
/// The document node is the instance while these are the "class" (or "blueprint").
|
||||||
fn static_nodes() -> Vec<DocumentNodeType> {
|
fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
vec![
|
vec![
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
|
|
@ -498,7 +501,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
name: "Frame",
|
name: "Frame",
|
||||||
data_type: FrontendGraphDataType::Raster,
|
data_type: FrontendGraphDataType::Raster,
|
||||||
}],
|
}],
|
||||||
properties: |_document_node, _node_id, _context| node_properties::string_properties("The graph's output is drawn in the layer"),
|
properties: |_document_node, _node_id, _context| node_properties::string_properties("Consumes the scope opened by the Begin Scope node and evaluates the contained node network"),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
|
|
@ -798,8 +801,11 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||||
category: "Image Adjustments",
|
category: "Image Adjustments",
|
||||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||||
inputs: vec![0],
|
inputs: vec![0],
|
||||||
outputs: vec![NodeOutput::new(4, 0), NodeOutput::new(1, 0), NodeOutput::new(2, 0), NodeOutput::new(3, 0), NodeOutput::new(4, 0)],
|
outputs: vec![NodeOutput::new(1, 0), NodeOutput::new(2, 0), NodeOutput::new(3, 0), NodeOutput::new(4, 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
|
// The input image feeds into the identity, then we take its passed-through value when the other channels are reading from it instead of the original input.
|
||||||
|
// We do this for technical restrictions imposed by Graphene which doesn't allow an input to feed into multiple interior nodes in the subgraph.
|
||||||
|
// Diagram: <https://files.keavon.com/-/AchingSecondHypsilophodon/capture.png>
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Identity".to_string(),
|
name: "Identity".to_string(),
|
||||||
inputs: vec![NodeInput::Network(concrete!(ImageFrame<Color>))],
|
inputs: vec![NodeInput::Network(concrete!(ImageFrame<Color>))],
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,25 @@ use core::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{Node, NodeMut};
|
use crate::{Node, NodeMut};
|
||||||
|
|
||||||
|
// This is how we can generically define composition of two nodes.
|
||||||
|
// This is done generically as shown: <https://files.keavon.com/-/SurprisedGaseousAnhinga/capture.png>
|
||||||
|
// A concrete example: <https://files.keavon.com/-/ExcitableGoldRay/capture.png>
|
||||||
|
// And showing the direction of data flow: <https://files.keavon.com/-/SoreShimmeringElephantseal/capture.png>
|
||||||
|
/// ┌────────────────┐
|
||||||
|
/// T │ │ U
|
||||||
|
/// ───────────►│ Compose Node ├───────────►
|
||||||
|
/// │ │
|
||||||
|
/// └────┬───────────┤
|
||||||
|
/// ┌──────────┐ │ │
|
||||||
|
/// │ │ T -> V │ │
|
||||||
|
/// │ First ├─────────────►│ │
|
||||||
|
/// │ │ │ │
|
||||||
|
/// └──────────┘ │ │
|
||||||
|
/// ┌──────────┐ │ │
|
||||||
|
/// │ │ V -> U │ │
|
||||||
|
/// │ Second ├─────────────►│ │
|
||||||
|
/// │ │ └───────────┘
|
||||||
|
/// └──────────┘
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct ComposeNode<First, Second, I> {
|
pub struct ComposeNode<First, Second, I> {
|
||||||
first: First,
|
first: First,
|
||||||
|
|
|
||||||
|
|
@ -102,12 +102,18 @@ impl PartialEq for TypeDescriptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Graph runtime type information used for type inference.
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, specta::Type)]
|
#[derive(Clone, PartialEq, Eq, Hash, specta::Type)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
|
/// A wrapper for some type variable used within the inference system. Resolved at inference time and replaced with a concrete type.
|
||||||
Generic(Cow<'static, str>),
|
Generic(Cow<'static, str>),
|
||||||
|
/// A wrapper around the Rust type id for any concrete Rust type. Allows us to do equality comparisons, like checking if a String == a String.
|
||||||
Concrete(TypeDescriptor),
|
Concrete(TypeDescriptor),
|
||||||
|
/// Runtime type information for a function. Given some input, gives some output.
|
||||||
|
/// See the example and explanation in the `ComposeNode` implementation within the node registry for more info.
|
||||||
Fn(Box<Type>, Box<Type>),
|
Fn(Box<Type>, Box<Type>),
|
||||||
|
/// Not used at the moment.
|
||||||
Future(Box<Type>),
|
Future(Box<Type>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,55 +141,55 @@ impl DocumentNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the possible inputs to a node.
|
/// Represents the possible inputs to a node.
|
||||||
///
|
|
||||||
/// # More about short circuting
|
|
||||||
///
|
|
||||||
/// In Graphite nodes are functions and by default, these are composed into a single function
|
|
||||||
/// by inserting Compose nodes.
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// ┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
|
||||||
/// │ │◄──────────────┤ │◄───────────────┤ │
|
|
||||||
/// │ A │ │ B │ │ C │
|
|
||||||
/// │ ├──────────────►│ ├───────────────►│ │
|
|
||||||
/// └─────────────────┘ └──────────────────┘ └──────────────────┘
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// This is equivalent to calling c(b(a(input))) when evaluating c with input ( `c.eval(input)`).
|
|
||||||
/// But sometimes we might want to have a little more control over the order of execution.
|
|
||||||
/// This is why we allow nodes to opt out of the input forwarding by consuming the input directly.
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// ┌─────────────────────┐ ┌─────────────┐
|
|
||||||
/// │ │◄───────────────┤ │
|
|
||||||
/// │ Cache Node │ │ C │
|
|
||||||
/// │ ├───────────────►│ │
|
|
||||||
/// ┌──────────────────┐ ├─────────────────────┤ └─────────────┘
|
|
||||||
/// │ │◄──────────────┤ │
|
|
||||||
/// │ A │ │ * Cached Node │
|
|
||||||
/// │ ├──────────────►│ │
|
|
||||||
/// └──────────────────┘ └─────────────────────┘
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// In this case the Cache node actually consumes its input and then manually forwards it to its parameter Node.
|
|
||||||
/// This is necessary because the Cache Node needs to short-circut the actual node evaluation.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash, DynAny)]
|
#[derive(Debug, Clone, PartialEq, Hash, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum NodeInput {
|
pub enum NodeInput {
|
||||||
Node {
|
/// A reference to another node in the same network from which this node can receive its input.
|
||||||
node_id: NodeId,
|
Node { node_id: NodeId, output_index: usize, lambda: bool },
|
||||||
output_index: usize,
|
|
||||||
lambda: bool,
|
/// A hardcoded value that can't change after the graph is compiled. Gets converted into a value node during graph compilation.
|
||||||
},
|
Value { tagged_value: TaggedValue, exposed: bool },
|
||||||
Value {
|
|
||||||
tagged_value: TaggedValue,
|
/// Input that is provided by the parent network to this document node, instead of from a hardcoded value or another node within the same network.
|
||||||
exposed: bool,
|
|
||||||
},
|
|
||||||
Network(Type),
|
Network(Type),
|
||||||
|
|
||||||
/// A short circuting input represents an input that is not resolved through function composition
|
/// A short circuting input represents an input that is not resolved through function composition
|
||||||
/// but actually consuming the provided input instead of passing it to its predecessor.
|
/// but rather by actually consuming the provided input instead of passing it to its predecessor.
|
||||||
/// See [NodeInput] docs for more explanation.
|
///
|
||||||
|
/// In Graphite nodes are functions, and by default these are composed into a single function
|
||||||
|
/// by automatic insertion of inserting Compose nodes.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||||
|
/// │ │◄───┤ │◄───┤ │
|
||||||
|
/// │ A │ │ B │ │ C │
|
||||||
|
/// │ ├───►│ ├───►│ │
|
||||||
|
/// └───────────────┘ └───────────────┘ └───────────────┘
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This is equivalent to calling c(b(a(input))) when evaluating c with input ( `c.eval(input)`).
|
||||||
|
/// But sometimes we might want to have a little more control over the order of execution.
|
||||||
|
/// This is why we allow nodes to opt out of the input forwarding by consuming the input directly.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// ┌───────────────┐ ┌───────────────┐
|
||||||
|
/// │ │◄───┤ │
|
||||||
|
/// │ Cache Node │ │ C │
|
||||||
|
/// │ ├───►│ │
|
||||||
|
/// ┌───────────────┐ ├───────────────┤ └───────────────┘
|
||||||
|
/// │ │◄───┤ │
|
||||||
|
/// │ A │ │ * Cached Node │
|
||||||
|
/// │ ├───►│ │
|
||||||
|
/// └───────────────┘ └───────────────┘
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In this case the Cache node actually consumes its input and then manually forwards it to its parameter Node.
|
||||||
|
/// This is necessary because the Cache Node needs to short-circut the actual node evaluation.
|
||||||
// TODO: Update
|
// TODO: Update
|
||||||
|
// ShortCircut(Type),
|
||||||
|
|
||||||
|
/// A Rust source code string. Allows us to insert literal Rust code. Only used for GPU compilation.
|
||||||
|
/// We can use this whenever we spin up Rustc. Sort of like inline assembly, but because our language is Rust, it acts as inline Rust.
|
||||||
Inline(InlineRust),
|
Inline(InlineRust),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -423,6 +423,8 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||||
node.into_type_erased()
|
node.into_type_erased()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
// This is how we can generically define composition of two nodes.
|
||||||
|
// See further details in the code definition for the `struct ComposeNode<First, Second, I> { ... }` struct.
|
||||||
NodeIOTypes::new(
|
NodeIOTypes::new(
|
||||||
generic!(T),
|
generic!(T),
|
||||||
generic!(U),
|
generic!(U),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue