Integrate the node graph as a Node Graph Frame layer type (#812)

* Add node graph frame tool

* Add a brighten

* Use the node graph

* Fix topological_sort

* Update UI

* Add icons for the tool and layer type

* Avoid serde & use bitmaps to improve performance

* Allow serialising a node graph

* Fix missing ..Default::default()

* Fix incorrect comments

* Cache node graph output image

* Suppress no-cycle import warning

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2022-11-05 14:38:14 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 596b9f4531
commit 9cdebfb1f0
33 changed files with 1018 additions and 258 deletions
+65 -49
View File
@@ -1,35 +1,49 @@
use std::collections::HashMap;
use std::collections::HashSet;
use crate::document::value;
use crate::document::NodeId;
#[derive(Debug, PartialEq)]
pub struct NodeIdentifier<'a> {
pub name: &'a str,
pub types: &'a [Type<'a>],
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NodeIdentifier {
pub name: std::borrow::Cow<'static, str>,
pub types: std::borrow::Cow<'static, [Type]>,
}
#[derive(Debug, PartialEq)]
pub enum Type<'a> {
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Type {
Generic,
Concrete(&'a str),
Concrete(std::borrow::Cow<'static, str>),
}
impl<'a> From<&'a str> for Type<'a> {
fn from(s: &'a str) -> Self {
Type::Concrete(s)
}
}
impl<'a> From<&'a str> for NodeIdentifier<'a> {
fn from(s: &'a str) -> Self {
NodeIdentifier { name: s, types: &[] }
impl From<&'static str> for Type {
fn from(s: &'static str) -> Self {
Type::Concrete(std::borrow::Cow::Borrowed(s))
}
}
impl<'a> NodeIdentifier<'a> {
pub fn new(name: &'a str, types: &'a [Type<'a>]) -> Self {
NodeIdentifier { name, types }
impl Type {
pub const fn from_str(concrete: &'static str) -> Self {
Type::Concrete(std::borrow::Cow::Borrowed(concrete))
}
}
impl From<&'static str> for NodeIdentifier {
fn from(s: &'static str) -> Self {
NodeIdentifier {
name: std::borrow::Cow::Borrowed(s),
types: std::borrow::Cow::Borrowed(&[]),
}
}
}
impl NodeIdentifier {
pub const fn new(name: &'static str, types: &'static [Type]) -> Self {
NodeIdentifier {
name: std::borrow::Cow::Borrowed(name),
types: std::borrow::Cow::Borrowed(types),
}
}
}
@@ -60,7 +74,7 @@ impl PartialEq for ConstructionArgs {
pub struct ProtoNode {
pub construction_args: ConstructionArgs,
pub input: ProtoNodeInput,
pub identifier: NodeIdentifier<'static>,
pub identifier: NodeIdentifier,
}
#[derive(Debug, Default, PartialEq, Eq)]
@@ -83,10 +97,7 @@ impl ProtoNodeInput {
impl ProtoNode {
pub fn value(value: ConstructionArgs) -> Self {
Self {
identifier: NodeIdentifier {
name: "graphene_core::value::ValueNode",
types: &[Type::Generic],
},
identifier: NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic]),
construction_args: value,
input: ProtoNodeInput::None,
}
@@ -110,7 +121,22 @@ impl ProtoNode {
}
impl ProtoNetwork {
fn reverse_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
pub fn collect_outwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for (id, node) in &self.nodes {
if let ProtoNodeInput::Node(ref_id) = &node.input {
edges.entry(*ref_id).or_default().push(*id)
}
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
for ref_id in ref_nodes {
edges.entry(*ref_id).or_default().push(*id)
}
}
}
edges
}
pub fn collect_inwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for (id, node) in &self.nodes {
if let ProtoNodeInput::Node(ref_id) = &node.input {
@@ -125,38 +151,28 @@ impl ProtoNetwork {
edges
}
// Based on https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
pub fn topological_sort(&self) -> Vec<NodeId> {
let mut visited = HashSet::new();
let mut stack = Vec::new();
let mut sorted = Vec::new();
let graph = self.reverse_edges();
// TODO: remove
println!("{:#?}", graph);
let outwards_edges = self.collect_outwards_edges();
let mut inwards_edges = self.collect_inwards_edges();
let mut no_incoming_edges: Vec<_> = self.nodes.iter().map(|entry| entry.0).filter(|id| !inwards_edges.contains_key(id)).collect();
for (id, _) in &self.nodes {
if !visited.contains(id) {
stack.push(*id);
assert_ne!(no_incoming_edges.len(), 0, "Acyclic graphs must have at least one node with no incoming edge");
while let Some(id) = stack.pop() {
//TODO remove
println!("{:?}", stack);
if !visited.contains(&id) {
visited.insert(id);
if let Some(refs) = graph.get(&id) {
for ref_id in refs {
if !visited.contains(ref_id) {
stack.push(id);
stack.push(*ref_id);
break;
}
}
}
sorted.push(id);
while let Some(node_id) = no_incoming_edges.pop() {
sorted.push(node_id);
if let Some(outwards_edges) = outwards_edges.get(&node_id) {
for &ref_id in outwards_edges {
let dependencies = inwards_edges.get_mut(&ref_id).unwrap();
dependencies.retain(|&id| id != node_id);
if dependencies.is_empty() {
no_incoming_edges.push(ref_id)
}
}
}
}
sorted.reverse();
sorted
}