mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Add type checking to the node graph (#1025)
* Implement type inference Add type hints to node trait Add type annotation infrastructure Refactor type ascription infrastructure Run cargo fix Insert infer types stub Remove types from node identifier * Implement covariance * Disable rejection of generic inputs + parameters * Fix lints * Extend type checking to cover Network inputs * Implement generic specialization * Relax covariance rules * Fix type annotations for TypErasedComposeNode * Fix type checking errors * Keep connection information during node resolution * Fix TypeDescriptor PartialEq implementation * Apply review suggestions * Add documentation to type inference * Add Imaginate node to document node types * Fix whitespace in macros * Add types to imaginate node * Fix type declaration for imaginate node + add console logging * Use fully qualified type names as fallback during comparison --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -11,7 +11,7 @@ serde = ["dep:serde", "graphene-core/serde", "glam/serde"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
graphene-core = { path = "../gcore", features = ["alloc"] }
|
||||
graphene-core = { path = "../gcore", features = ["std"] }
|
||||
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
|
||||
num-traits = "0.2"
|
||||
dyn-clone = "1.0"
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use crate::document::value::TaggedValue;
|
||||
use crate::generic;
|
||||
use crate::proto::{ConstructionArgs, NodeIdentifier, ProtoNetwork, ProtoNode, ProtoNodeInput, Type};
|
||||
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
|
||||
use graphene_core::{NodeIdentifier, Type};
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use glam::IVec2;
|
||||
use graphene_core::TypeDescriptor;
|
||||
use rand_chacha::{
|
||||
rand_core::{RngCore, SeedableRng},
|
||||
ChaCha20Rng,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Mutex;
|
||||
|
||||
@@ -59,7 +61,7 @@ impl DocumentNode {
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, input)| matches!(input, NodeInput::Network))
|
||||
.filter(|(_, input)| matches!(input, NodeInput::Network(_)))
|
||||
.nth(offset)
|
||||
.expect("no network input");
|
||||
|
||||
@@ -80,9 +82,9 @@ impl DocumentNode {
|
||||
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode.");
|
||||
(ProtoNodeInput::Node(node_id), ConstructionArgs::Nodes(vec![]))
|
||||
}
|
||||
NodeInput::Network => (ProtoNodeInput::Network, ConstructionArgs::Nodes(vec![])),
|
||||
NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])),
|
||||
};
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network)), "recieved non resolved parameter");
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter");
|
||||
assert!(
|
||||
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
|
||||
"recieved value as parameter. inupts: {:#?}, construction_args: {:#?}",
|
||||
@@ -129,12 +131,12 @@ impl DocumentNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, specta::Type)]
|
||||
#[derive(Debug, Clone, PartialEq, Hash, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum NodeInput {
|
||||
Node { node_id: NodeId, output_index: usize },
|
||||
Value { tagged_value: value::TaggedValue, exposed: bool },
|
||||
Network,
|
||||
Network(Type),
|
||||
}
|
||||
|
||||
impl NodeInput {
|
||||
@@ -153,17 +155,14 @@ impl NodeInput {
|
||||
match self {
|
||||
NodeInput::Node { .. } => true,
|
||||
NodeInput::Value { exposed, .. } => *exposed,
|
||||
NodeInput::Network => false,
|
||||
NodeInput::Network(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NodeInput {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (&self, &other) {
|
||||
(Self::Node { node_id: n0, output_index: o0 }, Self::Node { node_id: n1, output_index: o1 }) => n0 == n1 && o0 == o1,
|
||||
(Self::Value { tagged_value: v1, .. }, Self::Value { tagged_value: v2, .. }) => v1 == v2,
|
||||
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
||||
pub fn ty(&self) -> Type {
|
||||
match self {
|
||||
NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"),
|
||||
NodeInput::Value { tagged_value, .. } => tagged_value.ty(),
|
||||
NodeInput::Network(ty) => ty.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,7 +356,7 @@ impl NodeNetwork {
|
||||
.unwrap_or_else(|| panic!("The node which was supposed to be flattened does not exist in the network, id {} network {:#?}", node, self));
|
||||
|
||||
if self.disabled.contains(&id) {
|
||||
node.implementation = DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]));
|
||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
|
||||
node.inputs.drain(1..);
|
||||
self.nodes.insert(id, node);
|
||||
return;
|
||||
@@ -394,7 +393,7 @@ impl NodeNetwork {
|
||||
let value_node = DocumentNode {
|
||||
name,
|
||||
inputs: vec![NodeInput::Value { tagged_value, exposed }],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[generic!("T")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
};
|
||||
assert!(!self.nodes.contains_key(&new_id));
|
||||
@@ -402,7 +401,7 @@ impl NodeNetwork {
|
||||
let network_input = self.nodes.get_mut(network_input).unwrap();
|
||||
network_input.populate_first_network_input(new_id, 0, *offset);
|
||||
}
|
||||
NodeInput::Network => {
|
||||
NodeInput::Network(_) => {
|
||||
*network_offsets.get_mut(network_input).unwrap() += 1;
|
||||
if let Some(index) = self.inputs.iter().position(|i| *i == id) {
|
||||
self.inputs[index] = *network_input;
|
||||
@@ -410,7 +409,7 @@ impl NodeNetwork {
|
||||
}
|
||||
}
|
||||
}
|
||||
node.implementation = DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]));
|
||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
|
||||
node.inputs = inner_network
|
||||
.outputs
|
||||
.iter()
|
||||
@@ -419,6 +418,7 @@ impl NodeNetwork {
|
||||
output_index: node_output_index,
|
||||
})
|
||||
.collect();
|
||||
|
||||
for node_id in new_nodes {
|
||||
self.flatten_with_fns(node_id, map_ids, gen_id);
|
||||
}
|
||||
@@ -456,8 +456,8 @@ impl NodeNetwork {
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Input".into(),
|
||||
inputs: vec![NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
inputs: vec![NodeInput::Network(concrete!(u32))],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
metadata: DocumentNodeMetadata { position: (8, 4).into() },
|
||||
},
|
||||
),
|
||||
@@ -466,7 +466,7 @@ impl NodeNetwork {
|
||||
DocumentNode {
|
||||
name: "Output".into(),
|
||||
inputs: vec![NodeInput::node(output_node_id, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
metadata: DocumentNodeMetadata { position: (output_offset, 4).into() },
|
||||
},
|
||||
),
|
||||
@@ -553,7 +553,8 @@ impl NodeNetwork {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::proto::{ConstructionArgs, NodeIdentifier, ProtoNetwork, ProtoNode, ProtoNodeInput};
|
||||
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
|
||||
use graphene_core::NodeIdentifier;
|
||||
|
||||
fn gen_node_id() -> NodeId {
|
||||
static mut NODE_ID: NodeId = 3;
|
||||
@@ -572,9 +573,9 @@ mod test {
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(u32))],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
|
||||
},
|
||||
),
|
||||
(
|
||||
@@ -583,7 +584,7 @@ mod test {
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::node(0, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddNode".into()),
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -605,9 +606,9 @@ mod test {
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(u32))],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
|
||||
},
|
||||
),
|
||||
(
|
||||
@@ -616,7 +617,7 @@ mod test {
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::node(1, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddNode".into()),
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -637,7 +638,7 @@ mod test {
|
||||
DocumentNode {
|
||||
name: "Inc".into(),
|
||||
inputs: vec![
|
||||
NodeInput::Network,
|
||||
NodeInput::Network(concrete!(u32)),
|
||||
NodeInput::Value {
|
||||
tagged_value: value::TaggedValue::U32(2),
|
||||
exposed: false,
|
||||
@@ -663,15 +664,15 @@ mod test {
|
||||
fn resolve_proto_node_add() {
|
||||
let document_node = DocumentNode {
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::node(0, 0)],
|
||||
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::node(0, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
|
||||
};
|
||||
|
||||
let proto_node = document_node.resolve_proto_node();
|
||||
let reference = ProtoNode {
|
||||
identifier: NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")]),
|
||||
input: ProtoNodeInput::Network,
|
||||
identifier: "graphene_core::structural::ConsNode".into(),
|
||||
input: ProtoNodeInput::Network(concrete!(u32)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![0]),
|
||||
};
|
||||
assert_eq!(proto_node, reference);
|
||||
@@ -686,7 +687,7 @@ mod test {
|
||||
(
|
||||
1,
|
||||
ProtoNode {
|
||||
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]),
|
||||
identifier: "graphene_core::ops::IdNode".into(),
|
||||
input: ProtoNodeInput::Node(11),
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
},
|
||||
@@ -694,15 +695,15 @@ mod test {
|
||||
(
|
||||
10,
|
||||
ProtoNode {
|
||||
identifier: NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")]),
|
||||
input: ProtoNodeInput::Network,
|
||||
identifier: "graphene_core::structural::ConsNode".into(),
|
||||
input: ProtoNodeInput::Network(concrete!(u32)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![14]),
|
||||
},
|
||||
),
|
||||
(
|
||||
11,
|
||||
ProtoNode {
|
||||
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")]),
|
||||
identifier: "graphene_core::ops::AddNode".into(),
|
||||
input: ProtoNodeInput::Node(10),
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
},
|
||||
@@ -731,16 +732,16 @@ mod test {
|
||||
name: "Inc".into(),
|
||||
inputs: vec![NodeInput::node(11, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
},
|
||||
),
|
||||
(
|
||||
10,
|
||||
DocumentNode {
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::node(14, 0)],
|
||||
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::node(14, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
|
||||
},
|
||||
),
|
||||
(
|
||||
@@ -752,7 +753,7 @@ mod test {
|
||||
exposed: false,
|
||||
}],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[generic!("T")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()),
|
||||
},
|
||||
),
|
||||
(
|
||||
@@ -761,7 +762,7 @@ mod test {
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::node(10, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddNode".into()),
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -780,18 +781,18 @@ mod test {
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "Identity 1".into(),
|
||||
inputs: vec![NodeInput::Network],
|
||||
inputs: vec![NodeInput::Network(concrete!(u32))],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
|
||||
},
|
||||
),
|
||||
(
|
||||
2,
|
||||
DocumentNode {
|
||||
name: "Identity 2".into(),
|
||||
inputs: vec![NodeInput::Network],
|
||||
inputs: vec![NodeInput::Network(concrete!(u32))],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -821,7 +822,7 @@ mod test {
|
||||
name: "Result".into(),
|
||||
inputs: vec![result_node_input],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ use dyn_any::{DynAny, Upcast};
|
||||
use dyn_clone::DynClone;
|
||||
pub use glam::{DAffine2, DVec2};
|
||||
use graphene_core::raster::LuminanceCalculation;
|
||||
use graphene_core::Node;
|
||||
use graphene_core::{Node, Type};
|
||||
use std::hash::Hash;
|
||||
pub use std::sync::Arc;
|
||||
|
||||
@@ -142,6 +142,32 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::LayerPath(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> Type {
|
||||
use graphene_core::TypeDescriptor;
|
||||
use std::borrow::Cow;
|
||||
match self {
|
||||
TaggedValue::None => concrete!(()),
|
||||
TaggedValue::String(_) => concrete!(String),
|
||||
TaggedValue::U32(_) => concrete!(u32),
|
||||
TaggedValue::F32(_) => concrete!(f32),
|
||||
TaggedValue::F64(_) => concrete!(f64),
|
||||
TaggedValue::Bool(_) => concrete!(bool),
|
||||
TaggedValue::DVec2(_) => concrete!(DVec2),
|
||||
TaggedValue::OptionalDVec2(_) => concrete!(Option<DVec2>),
|
||||
TaggedValue::Image(_) => concrete!(graphene_core::raster::Image),
|
||||
TaggedValue::RcImage(_) => concrete!(Option<Arc<graphene_core::raster::Image>>),
|
||||
TaggedValue::Color(_) => concrete!(graphene_core::raster::Color),
|
||||
TaggedValue::Subpath(_) => concrete!(graphene_core::vector::subpath::Subpath),
|
||||
TaggedValue::RcSubpath(_) => concrete!(Arc<graphene_core::vector::subpath::Subpath>),
|
||||
TaggedValue::ImaginateSamplingMethod(_) => concrete!(ImaginateSamplingMethod),
|
||||
TaggedValue::ImaginateMaskStartingFill(_) => concrete!(ImaginateMaskStartingFill),
|
||||
TaggedValue::ImaginateStatus(_) => concrete!(ImaginateStatus),
|
||||
TaggedValue::LayerPath(_) => concrete!(Option<Vec<u64>>),
|
||||
TaggedValue::DAffine2(_) => concrete!(DAffine2),
|
||||
TaggedValue::LuminanceCalculation(_) => concrete!(LuminanceCalculation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UpcastNode {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[macro_use]
|
||||
extern crate graphene_core;
|
||||
pub use graphene_core::{concrete, generic, NodeIdentifier, Type, TypeDescriptor};
|
||||
|
||||
pub mod document;
|
||||
pub mod proto;
|
||||
|
||||
|
||||
@@ -4,87 +4,16 @@ use std::hash::Hash;
|
||||
|
||||
use crate::document::value;
|
||||
use crate::document::NodeId;
|
||||
use dyn_any::DynAny;
|
||||
use graphene_core::*;
|
||||
use std::pin::Pin;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! concrete {
|
||||
($type:expr) => {
|
||||
Type::Concrete(std::borrow::Cow::Borrowed($type))
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! generic {
|
||||
($type:expr) => {
|
||||
Type::Generic(std::borrow::Cow::Borrowed($type))
|
||||
};
|
||||
}
|
||||
pub type Any<'n> = Box<dyn DynAny<'n> + 'n>;
|
||||
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync;
|
||||
pub type TypeErasedPinnedRef<'n> = Pin<&'n (dyn for<'i> NodeIO<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync)>;
|
||||
pub type TypeErasedPinned<'n> = Pin<Box<dyn for<'i> NodeIO<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync>>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, specta::Type)]
|
||||
#[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]>,
|
||||
}
|
||||
|
||||
impl NodeIdentifier {
|
||||
pub fn fully_qualified_name(&self) -> String {
|
||||
let mut name = String::new();
|
||||
name.push_str(self.name.as_ref());
|
||||
name.push('<');
|
||||
for t in self.types.as_ref() {
|
||||
name.push_str(t.to_string().as_str());
|
||||
name.push_str(", ");
|
||||
}
|
||||
name.pop();
|
||||
name.pop();
|
||||
name.push('>');
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Type {
|
||||
Generic(std::borrow::Cow<'static, str>),
|
||||
Concrete(std::borrow::Cow<'static, str>),
|
||||
}
|
||||
|
||||
impl From<&'static str> for Type {
|
||||
fn from(s: &'static str) -> Self {
|
||||
Type::Concrete(std::borrow::Cow::Borrowed(s))
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Type::Generic(name) => write!(f, "{}", name),
|
||||
Type::Concrete(name) => write!(f, "{}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type NodeConstructor = for<'a> fn(Vec<TypeErasedPinnedRef<'static>>) -> TypeErasedPinned<'static>;
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct ProtoNetwork {
|
||||
@@ -140,11 +69,10 @@ pub struct ProtoNode {
|
||||
pub identifier: NodeIdentifier,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum ProtoNodeInput {
|
||||
None,
|
||||
#[default]
|
||||
Network,
|
||||
Network(Type),
|
||||
Node(NodeId),
|
||||
}
|
||||
|
||||
@@ -161,11 +89,14 @@ impl ProtoNode {
|
||||
pub fn stable_node_id(&self) -> Option<NodeId> {
|
||||
use std::hash::Hasher;
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
self.identifier.fully_qualified_name().hash(&mut hasher);
|
||||
self.identifier.name.hash(&mut hasher);
|
||||
self.construction_args.hash(&mut hasher);
|
||||
match self.input {
|
||||
ProtoNodeInput::None => "none".hash(&mut hasher),
|
||||
ProtoNodeInput::Network => "network".hash(&mut hasher),
|
||||
ProtoNodeInput::Network(ref ty) => {
|
||||
"network".hash(&mut hasher);
|
||||
ty.hash(&mut hasher);
|
||||
}
|
||||
ProtoNodeInput::Node(id) => id.hash(&mut hasher),
|
||||
};
|
||||
Some(hasher.finish() as NodeId)
|
||||
@@ -173,7 +104,7 @@ impl ProtoNode {
|
||||
|
||||
pub fn value(value: ConstructionArgs) -> Self {
|
||||
Self {
|
||||
identifier: NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic(Cow::Borrowed("T"))]),
|
||||
identifier: NodeIdentifier::new("graphene_core::value::ValueNode"),
|
||||
construction_args: value,
|
||||
input: ProtoNodeInput::None,
|
||||
}
|
||||
@@ -260,20 +191,22 @@ impl ProtoNetwork {
|
||||
}
|
||||
|
||||
pub fn resolve_inputs(&mut self) {
|
||||
while !self.resolve_inputs_impl() {}
|
||||
let mut resolved = HashSet::new();
|
||||
while !self.resolve_inputs_impl(&mut resolved) {}
|
||||
}
|
||||
fn resolve_inputs_impl(&mut self) -> bool {
|
||||
fn resolve_inputs_impl(&mut self, resolved: &mut HashSet<NodeId>) -> bool {
|
||||
self.reorder_ids();
|
||||
|
||||
let mut lookup = self.nodes.iter().map(|(id, _)| (*id, *id)).collect::<HashMap<_, _>>();
|
||||
let compose_node_id = self.nodes.len() as NodeId;
|
||||
let inputs = self.nodes.iter().map(|(_, node)| node.input).collect::<Vec<_>>();
|
||||
let inputs = self.nodes.iter().map(|(_, node)| node.input.clone()).collect::<Vec<_>>();
|
||||
|
||||
if let Some((input_node, id, input)) = self.nodes.iter_mut().find_map(|(id, node)| {
|
||||
let resolved_lookup = resolved.clone();
|
||||
if let Some((input_node, id, input)) = self.nodes.iter_mut().filter(|(id, _)| !resolved_lookup.contains(id)).find_map(|(id, node)| {
|
||||
if let ProtoNodeInput::Node(input_node) = node.input {
|
||||
node.input = ProtoNodeInput::None;
|
||||
resolved.insert(*id);
|
||||
let pre_node_input = inputs.get(input_node as usize).expect("input node should exist");
|
||||
Some((input_node, *id, *pre_node_input))
|
||||
Some((input_node, *id, pre_node_input.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -283,7 +216,7 @@ impl ProtoNetwork {
|
||||
self.nodes.push((
|
||||
compose_node_id,
|
||||
ProtoNode {
|
||||
identifier: NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>", &[generic!("T"), generic!("U")]),
|
||||
identifier: NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"),
|
||||
construction_args: ConstructionArgs::Nodes(vec![input_node, id]),
|
||||
input,
|
||||
},
|
||||
@@ -373,6 +306,172 @@ impl ProtoNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
/// The `TypingContext` is used to store the types of the nodes indexed by their stable node id.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct TypingContext {
|
||||
lookup: Cow<'static, HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>>,
|
||||
inferred: HashMap<NodeId, NodeIOTypes>,
|
||||
constructor: HashMap<NodeId, NodeConstructor>,
|
||||
}
|
||||
|
||||
impl TypingContext {
|
||||
/// Creates a new `TypingContext` with the given lookup table.
|
||||
pub fn new(lookup: &'static HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>) -> Self {
|
||||
Self {
|
||||
lookup: Cow::Borrowed(lookup),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the `TypingContext` wtih a given proto network. This will infer the types of the nodes
|
||||
/// and store them in the `inferred` field. The proto network has to be topologically sorted
|
||||
/// and contain fully resolved stable node ids.
|
||||
pub fn update(&mut self, network: &ProtoNetwork) -> Result<(), String> {
|
||||
for (id, node) in network.nodes.iter() {
|
||||
self.infer(*id, node)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the node constructor for a given node id.
|
||||
pub fn constructor(&self, node_id: NodeId) -> Option<NodeConstructor> {
|
||||
self.constructor.get(&node_id).copied()
|
||||
}
|
||||
|
||||
/// Returns the inferred types for a given node id.
|
||||
pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<NodeIOTypes, String> {
|
||||
let identifier = node.identifier.name.clone();
|
||||
|
||||
// Return the inferred type if it is already known
|
||||
if let Some(infered) = self.inferred.get(&node_id) {
|
||||
return Ok(infered.clone());
|
||||
}
|
||||
|
||||
let parameters = match node.construction_args {
|
||||
// If the node has a value parameter we can infer the return type from it
|
||||
ConstructionArgs::Value(ref v) => {
|
||||
assert!(matches!(node.input, ProtoNodeInput::None));
|
||||
let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![]);
|
||||
self.inferred.insert(node_id, types.clone());
|
||||
return Ok(types);
|
||||
}
|
||||
// If the node has nodes as parameters we can infer the types from the node outputs
|
||||
ConstructionArgs::Nodes(ref nodes) => nodes
|
||||
.iter()
|
||||
.map(|id| {
|
||||
self.inferred
|
||||
.get(id)
|
||||
.ok_or(format!("Inferring type of {node_id} depends on {id} which is not present in the typing context"))
|
||||
.map(|node| node.output.clone())
|
||||
})
|
||||
.collect::<Result<Vec<Type>, String>>()?,
|
||||
};
|
||||
|
||||
// Get the node input type from the proto node declaration
|
||||
let input = match node.input {
|
||||
ProtoNodeInput::None => concrete!(()),
|
||||
ProtoNodeInput::Network(ref ty) => ty.clone(),
|
||||
ProtoNodeInput::Node(id) => {
|
||||
let input = self
|
||||
.inferred
|
||||
.get(&id)
|
||||
.ok_or(format!("Inferring type of {node_id} depends on {id} which is not present in the typing context"))?;
|
||||
input.output.clone()
|
||||
}
|
||||
};
|
||||
let impls = self.lookup.get(&node.identifier).ok_or(format!("No implementations found for {:?}", node.identifier))?;
|
||||
|
||||
if matches!(input, Type::Generic(_)) {
|
||||
return Err(format!("Generic types are not supported as inputs yet {:?} occured in {:?}", &input, node.identifier));
|
||||
}
|
||||
if parameters.iter().any(|p| matches!(p, Type::Generic(_))) {
|
||||
return Err(format!("Generic types are not supported in parameters: {:?} occured in {:?}", parameters, node.identifier));
|
||||
}
|
||||
let covariant = |output, input| match (&output, &input) {
|
||||
(Type::Concrete(t1), Type::Concrete(t2)) => t1 == t2,
|
||||
(Type::Concrete(_), Type::Generic(_)) => true,
|
||||
// TODO: relax this requirement when allowing generic types as inputs
|
||||
(Type::Generic(_), _) => false,
|
||||
};
|
||||
|
||||
// List of all implementations that match the input and parameter types
|
||||
let valid_output_types = impls
|
||||
.keys()
|
||||
.filter(|node_io| covariant(input.clone(), node_io.input.clone()) && parameters.iter().zip(node_io.parameters.iter()).all(|(p1, p2)| covariant(p1.clone(), p2.clone())))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Attempt to substitute generic types with concrete types and save the list of results
|
||||
let substitution_results = valid_output_types
|
||||
.iter()
|
||||
.map(|node_io| {
|
||||
collect_generics(node_io)
|
||||
.iter()
|
||||
.try_for_each(|generic| check_generic(node_io, &input, ¶meters, generic).map(|_| ()))
|
||||
.map(|_| {
|
||||
if let Type::Generic(out) = &node_io.output {
|
||||
((*node_io).clone(), check_generic(node_io, &input, ¶meters, out).unwrap())
|
||||
} else {
|
||||
((*node_io).clone(), node_io.output.clone())
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Collect all substitutions that are valid
|
||||
let valid_impls = substitution_results.iter().filter_map(|result| result.as_ref().ok()).collect::<Vec<_>>();
|
||||
|
||||
match valid_impls.as_slice() {
|
||||
[] => {
|
||||
dbg!(&self.inferred);
|
||||
Err(format!(
|
||||
"No implementations found for {identifier} with \ninput: {input:?} and \nparameters: {parameters:?}.\nOther Implementations found: {:?}",
|
||||
impls,
|
||||
))
|
||||
}
|
||||
[(org_nio, output)] => {
|
||||
let node_io = NodeIOTypes::new(input, (*output).clone(), parameters);
|
||||
|
||||
// Save the inferred type
|
||||
self.inferred.insert(node_id, node_io.clone());
|
||||
self.constructor.insert(node_id, impls[org_nio]);
|
||||
Ok(node_io)
|
||||
}
|
||||
_ => Err(format!(
|
||||
"Multiple implementations found for {identifier} with input {input:?} and parameters {parameters:?} (valid types: {valid_output_types:?}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of all generic types used in the node
|
||||
fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
|
||||
let inputs = [&types.input].into_iter().chain(types.parameters.iter());
|
||||
let mut generics = inputs
|
||||
.filter_map(|t| match t {
|
||||
Type::Generic(out) => Some(out.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if let Type::Generic(out) = &types.output {
|
||||
generics.push(out.clone());
|
||||
}
|
||||
generics.dedup();
|
||||
generics
|
||||
}
|
||||
|
||||
/// Checks if a generic type can be substituted with a concrete type and returns the concrete type
|
||||
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result<Type, String> {
|
||||
let inputs = [(&types.input, input)].into_iter().chain(types.parameters.iter().zip(parameters.iter()));
|
||||
let mut concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Type::Generic(input) if generic == input));
|
||||
let (_, out_ty) = concrete_inputs
|
||||
.next()
|
||||
.ok_or_else(|| format!("Generic output type {generic} is not dependent on input {input:?} or parameters {parameters:?}",))?;
|
||||
if concrete_inputs.any(|(_, ty)| ty != out_ty) {
|
||||
return Err(format!("Generic output type {generic} is dependent on multiple inputs or parameters",));
|
||||
}
|
||||
Ok(out_ty.clone())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@@ -436,12 +535,12 @@ mod test {
|
||||
assert_eq!(
|
||||
ids,
|
||||
vec![
|
||||
17495035641492238530,
|
||||
14931179783740213471,
|
||||
2268573767208263092,
|
||||
14616574692620381527,
|
||||
12110007198416821768,
|
||||
11185814750012198757
|
||||
15907139529964845467,
|
||||
17186311536944112733,
|
||||
1674503539363691855,
|
||||
10408773954839245246,
|
||||
1677533587730447846,
|
||||
6826908746727711035
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -471,7 +570,7 @@ mod test {
|
||||
10,
|
||||
ProtoNode {
|
||||
identifier: "cons".into(),
|
||||
input: ProtoNodeInput::Network,
|
||||
input: ProtoNodeInput::Network(concrete!(u32)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![14]),
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user