mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 19:08:05 +08:00
Implement the Brush tool (#1099)
* Implement Brush Node * Add color Input * Add VectorPointsNode * Add Erase Node * Adapt compilation infrastructure to allow non Image Frame inputs * Remove debug output from TransformNode * Fix transform calculation * Fix Blending by making the brush texture use associated alpha * Code improvements and UX polish * Rename Opacity to Flow * Add erase option to brush node + fix freehand tool * Fix crash * Revert erase implementation * Fix flattening id calculation * Fix some transformation issues * Fix changing the pivot location * Fix vector data modify bounds * Minor fn name cleanup * Fix some tests * Fix tests --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
committed by
Keavon Chambers
parent
758f757775
commit
589ff9a2d3
@@ -47,7 +47,7 @@ impl DocumentNode {
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, input)| matches!(input, NodeInput::Network(_)))
|
||||
.filter(|(_, input)| matches!(input, NodeInput::Network(_) | NodeInput::ShortCircut(_)))
|
||||
.nth(offset)
|
||||
.unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}"));
|
||||
|
||||
@@ -72,6 +72,7 @@ impl DocumentNode {
|
||||
NodeInput::ShortCircut(ty) => (ProtoNodeInput::ShortCircut(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::ShortCircut(_))), "recieved non resolved parameter");
|
||||
assert!(
|
||||
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
|
||||
"recieved value as parameter. inupts: {:#?}, construction_args: {:#?}",
|
||||
@@ -218,6 +219,12 @@ pub enum DocumentNodeImplementation {
|
||||
Unresolved(NodeIdentifier),
|
||||
}
|
||||
|
||||
impl Default for DocumentNodeImplementation {
|
||||
fn default() -> Self {
|
||||
Self::Unresolved(NodeIdentifier::new("graphene_cored::ops::IdNode"))
|
||||
}
|
||||
}
|
||||
|
||||
impl DocumentNodeImplementation {
|
||||
pub fn get_network(&self) -> Option<&NodeNetwork> {
|
||||
if let DocumentNodeImplementation::Network(n) = self {
|
||||
@@ -260,6 +267,227 @@ pub struct NodeNetwork {
|
||||
pub previous_outputs: Option<Vec<NodeOutput>>,
|
||||
}
|
||||
|
||||
/// Graph modification functions
|
||||
impl NodeNetwork {
|
||||
/// Get the original output nodes of this network, ignoring any preview node
|
||||
pub fn original_outputs(&self) -> &Vec<NodeOutput> {
|
||||
self.previous_outputs.as_ref().unwrap_or(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn input_types<'a>(&'a self) -> impl Iterator<Item = Type> + 'a {
|
||||
self.inputs.iter().map(move |id| self.nodes[id].inputs.get(0).map(|i| i.ty().clone()).unwrap_or(concrete!(())))
|
||||
}
|
||||
|
||||
/// An empty graph
|
||||
pub fn value_network(node: DocumentNode) -> Self {
|
||||
Self {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(0, 0)],
|
||||
nodes: [(0, node)].into_iter().collect(),
|
||||
disabled: vec![],
|
||||
previous_outputs: None,
|
||||
}
|
||||
}
|
||||
/// A graph with just an input node
|
||||
pub fn new_network() -> Self {
|
||||
Self {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(0, 0)],
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Input Frame".into(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(u32))],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
metadata: DocumentNodeMetadata { position: (8, 4).into() },
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends a new node to the network after the output node and sets it as the new output
|
||||
pub fn push_node(&mut self, mut node: DocumentNode) -> NodeId {
|
||||
let id = self.nodes.len().try_into().expect("Too many nodes in network");
|
||||
// Set the correct position for the new node
|
||||
if let Some(pos) = self.nodes.get(&self.original_outputs()[0].node_id).map(|n| n.metadata.position) {
|
||||
node.metadata.position = pos + IVec2::new(8, 0);
|
||||
}
|
||||
if self.outputs.is_empty() {
|
||||
self.outputs.push(NodeOutput::new(id, 0));
|
||||
}
|
||||
let input = NodeInput::node(self.outputs[0].node_id, self.outputs[0].node_output_index);
|
||||
if node.inputs.is_empty() {
|
||||
node.inputs.push(input);
|
||||
} else {
|
||||
node.inputs[0] = input;
|
||||
}
|
||||
self.nodes.insert(id, node);
|
||||
self.outputs = vec![NodeOutput::new(id, 0)];
|
||||
id
|
||||
}
|
||||
|
||||
/// Adds a output identity node to the network
|
||||
pub fn push_output_node(&mut self) -> NodeId {
|
||||
let node = DocumentNode {
|
||||
name: "Output".into(),
|
||||
inputs: vec![],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
metadata: DocumentNodeMetadata { position: (0, 0).into() },
|
||||
};
|
||||
self.push_node(node)
|
||||
}
|
||||
|
||||
/// Adds a Cache and a Clone node to the network
|
||||
pub fn push_cache_node(&mut self, ty: Type) -> NodeId {
|
||||
let node = DocumentNode {
|
||||
name: "Cache".into(),
|
||||
inputs: vec![],
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: vec![
|
||||
(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "CacheNode".to_string(),
|
||||
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::Network(ty)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
|
||||
metadata: Default::default(),
|
||||
},
|
||||
),
|
||||
(
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "CloneNode".to_string(),
|
||||
inputs: vec![NodeInput::node(0, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::CloneNode<_>")),
|
||||
metadata: Default::default(),
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
metadata: DocumentNodeMetadata { position: (0, 0).into() },
|
||||
};
|
||||
self.push_node(node)
|
||||
}
|
||||
|
||||
/// Get the nested network given by the path of node ids
|
||||
pub fn nested_network(&self, nested_path: &[NodeId]) -> Option<&Self> {
|
||||
let mut network = Some(self);
|
||||
|
||||
for segment in nested_path {
|
||||
network = network.and_then(|network| network.nodes.get(segment)).and_then(|node| node.implementation.get_network());
|
||||
}
|
||||
network
|
||||
}
|
||||
|
||||
/// Get the mutable nested network given by the path of node ids
|
||||
pub fn nested_network_mut(&mut self, nested_path: &[NodeId]) -> Option<&mut Self> {
|
||||
let mut network = Some(self);
|
||||
|
||||
for segment in nested_path {
|
||||
network = network.and_then(|network| network.nodes.get_mut(segment)).and_then(|node| node.implementation.get_network_mut());
|
||||
}
|
||||
network
|
||||
}
|
||||
|
||||
/// Check if the specified node id is connected to the output
|
||||
pub fn connected_to_output(&self, target_node_id: NodeId, ignore_imaginate: bool) -> bool {
|
||||
// If the node is the output then return true
|
||||
if self.outputs.iter().any(|&NodeOutput { node_id, .. }| node_id == target_node_id) {
|
||||
return true;
|
||||
}
|
||||
// Get the outputs
|
||||
let Some(mut stack) = self.outputs.iter().map(|&output| self.nodes.get(&output.node_id)).collect::<Option<Vec<_>>>() else {
|
||||
return false;
|
||||
};
|
||||
let mut already_visited = HashSet::new();
|
||||
already_visited.extend(self.outputs.iter().map(|output| output.node_id));
|
||||
|
||||
while let Some(node) = stack.pop() {
|
||||
// Skip the imaginate node inputs
|
||||
if ignore_imaginate && node.name == "Imaginate" {
|
||||
continue;
|
||||
}
|
||||
|
||||
for input in &node.inputs {
|
||||
if let &NodeInput::Node { node_id: ref_id, .. } = input {
|
||||
// Skip if already viewed
|
||||
if already_visited.contains(&ref_id) {
|
||||
continue;
|
||||
}
|
||||
// If the target node is used as input then return true
|
||||
if ref_id == target_node_id {
|
||||
return true;
|
||||
}
|
||||
// Add the referenced node to the stack
|
||||
let Some(ref_node) = self.nodes.get(&ref_id) else {
|
||||
continue;
|
||||
};
|
||||
already_visited.insert(ref_id);
|
||||
stack.push(ref_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Is the node being used directly as an output?
|
||||
pub fn outputs_contain(&self, node_id: NodeId) -> bool {
|
||||
self.outputs.iter().any(|output| output.node_id == node_id)
|
||||
}
|
||||
|
||||
/// Is the node being used directly as an original output?
|
||||
pub fn original_outputs_contain(&self, node_id: NodeId) -> bool {
|
||||
self.original_outputs().iter().any(|output| output.node_id == node_id)
|
||||
}
|
||||
|
||||
/// Is the node being used directly as a previous output?
|
||||
pub fn previous_outputs_contain(&self, node_id: NodeId) -> Option<bool> {
|
||||
self.previous_outputs.as_ref().map(|outputs| outputs.iter().any(|output| output.node_id == node_id))
|
||||
}
|
||||
|
||||
/// A iterator of all nodes connected by primary inputs.
|
||||
///
|
||||
/// Used for the properties panel and tools.
|
||||
pub fn primary_flow(&self) -> impl Iterator<Item = (&DocumentNode, u64)> {
|
||||
struct FlowIter<'a> {
|
||||
stack: Vec<NodeId>,
|
||||
network: &'a NodeNetwork,
|
||||
}
|
||||
impl<'a> Iterator for FlowIter<'a> {
|
||||
type Item = (&'a DocumentNode, NodeId);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
let node_id = self.stack.pop()?;
|
||||
if let Some(document_node) = self.network.nodes.get(&node_id) {
|
||||
self.stack.extend(
|
||||
document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.take(1) // Only show the primary input
|
||||
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
|
||||
);
|
||||
return Some((document_node, node_id));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
FlowIter {
|
||||
stack: self.outputs.iter().map(|output| output.node_id).collect(),
|
||||
network: &self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions for compiling the network
|
||||
impl NodeNetwork {
|
||||
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) {
|
||||
self.inputs.iter_mut().for_each(|id| *id = f(*id));
|
||||
@@ -405,6 +633,31 @@ impl NodeNetwork {
|
||||
self.nodes.insert(id, node);
|
||||
return;
|
||||
}
|
||||
// replace value inputs with value nodes
|
||||
for input in &mut node.inputs {
|
||||
let mut dummy_input = NodeInput::ShortCircut(concrete!(()));
|
||||
std::mem::swap(&mut dummy_input, input);
|
||||
if let NodeInput::Value { tagged_value, exposed } = dummy_input {
|
||||
let value_node_id = gen_id();
|
||||
let value_node_id = map_ids(id, value_node_id);
|
||||
self.nodes.insert(
|
||||
value_node_id,
|
||||
DocumentNode {
|
||||
name: "Value".into(),
|
||||
inputs: vec![NodeInput::Value { tagged_value, exposed }],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
);
|
||||
*input = NodeInput::Node {
|
||||
node_id: value_node_id,
|
||||
output_index: 0,
|
||||
lambda: false,
|
||||
};
|
||||
} else {
|
||||
*input = dummy_input;
|
||||
}
|
||||
}
|
||||
|
||||
match node.implementation {
|
||||
DocumentNodeImplementation::Network(mut inner_network) => {
|
||||
@@ -423,20 +676,6 @@ impl NodeNetwork {
|
||||
let network_input = self.nodes.get_mut(network_input).unwrap();
|
||||
network_input.populate_first_network_input(node_id, output_index, *offset, lambda);
|
||||
}
|
||||
NodeInput::Value { tagged_value, exposed } => {
|
||||
let name = "Value".to_string();
|
||||
let new_id = map_ids(id, gen_id());
|
||||
let value_node = DocumentNode {
|
||||
name,
|
||||
inputs: vec![NodeInput::Value { tagged_value, exposed }],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
};
|
||||
assert!(!self.nodes.contains_key(&new_id));
|
||||
self.nodes.insert(new_id, value_node);
|
||||
let network_input = self.nodes.get_mut(network_input).unwrap();
|
||||
network_input.populate_first_network_input(new_id, 0, *offset, false);
|
||||
}
|
||||
NodeInput::Network(_) => {
|
||||
*network_offsets.get_mut(network_input).unwrap() += 1;
|
||||
if let Some(index) = self.inputs.iter().position(|i| *i == id) {
|
||||
@@ -444,6 +683,7 @@ impl NodeNetwork {
|
||||
}
|
||||
}
|
||||
NodeInput::ShortCircut(_) => (),
|
||||
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
|
||||
}
|
||||
}
|
||||
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
|
||||
@@ -461,7 +701,7 @@ impl NodeNetwork {
|
||||
self.flatten_with_fns(node_id, map_ids, gen_id);
|
||||
}
|
||||
}
|
||||
DocumentNodeImplementation::Unresolved(_) => (),
|
||||
DocumentNodeImplementation::Unresolved(_) => {}
|
||||
}
|
||||
assert!(!self.nodes.contains_key(&id), "Trying to insert a node into the network caused an id conflict");
|
||||
self.nodes.insert(id, node);
|
||||
@@ -478,151 +718,6 @@ impl NodeNetwork {
|
||||
nodes: nodes.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the original output nodes of this network, ignoring any preview node
|
||||
pub fn original_outputs(&self) -> &Vec<NodeOutput> {
|
||||
self.previous_outputs.as_ref().unwrap_or(&self.outputs)
|
||||
}
|
||||
|
||||
/// A graph with just an input and output node
|
||||
pub fn new_network(output_offset: i32, output_node_id: NodeId) -> Self {
|
||||
Self {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Input Frame".into(),
|
||||
inputs: vec![NodeInput::Network(concrete!(u32))],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
metadata: DocumentNodeMetadata { position: (8, 4).into() },
|
||||
},
|
||||
),
|
||||
(
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "Output".into(),
|
||||
inputs: vec![NodeInput::node(output_node_id, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
|
||||
metadata: DocumentNodeMetadata { position: (output_offset, 4).into() },
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the nested network given by the path of node ids
|
||||
pub fn nested_network(&self, nested_path: &[NodeId]) -> Option<&Self> {
|
||||
let mut network = Some(self);
|
||||
|
||||
for segment in nested_path {
|
||||
network = network.and_then(|network| network.nodes.get(segment)).and_then(|node| node.implementation.get_network());
|
||||
}
|
||||
network
|
||||
}
|
||||
|
||||
/// Get the mutable nested network given by the path of node ids
|
||||
pub fn nested_network_mut(&mut self, nested_path: &[NodeId]) -> Option<&mut Self> {
|
||||
let mut network = Some(self);
|
||||
|
||||
for segment in nested_path {
|
||||
network = network.and_then(|network| network.nodes.get_mut(segment)).and_then(|node| node.implementation.get_network_mut());
|
||||
}
|
||||
network
|
||||
}
|
||||
|
||||
/// Check if the specified node id is connected to the output
|
||||
pub fn connected_to_output(&self, target_node_id: NodeId, ignore_imaginate: bool) -> bool {
|
||||
// If the node is the output then return true
|
||||
if self.outputs.iter().any(|&NodeOutput { node_id, .. }| node_id == target_node_id) {
|
||||
return true;
|
||||
}
|
||||
// Get the outputs
|
||||
let Some(mut stack) = self.outputs.iter().map(|&output| self.nodes.get(&output.node_id)).collect::<Option<Vec<_>>>() else {
|
||||
return false;
|
||||
};
|
||||
let mut already_visited = HashSet::new();
|
||||
already_visited.extend(self.outputs.iter().map(|output| output.node_id));
|
||||
|
||||
while let Some(node) = stack.pop() {
|
||||
// Skip the imaginate node inputs
|
||||
if ignore_imaginate && node.name == "Imaginate" {
|
||||
continue;
|
||||
}
|
||||
|
||||
for input in &node.inputs {
|
||||
if let &NodeInput::Node { node_id: ref_id, .. } = input {
|
||||
// Skip if already viewed
|
||||
if already_visited.contains(&ref_id) {
|
||||
continue;
|
||||
}
|
||||
// If the target node is used as input then return true
|
||||
if ref_id == target_node_id {
|
||||
return true;
|
||||
}
|
||||
// Add the referenced node to the stack
|
||||
let Some(ref_node) = self.nodes.get(&ref_id) else {
|
||||
continue;
|
||||
};
|
||||
already_visited.insert(ref_id);
|
||||
stack.push(ref_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Is the node being used directly as an output?
|
||||
pub fn outputs_contain(&self, node_id: NodeId) -> bool {
|
||||
self.outputs.iter().any(|output| output.node_id == node_id)
|
||||
}
|
||||
|
||||
/// Is the node being used directly as an original output?
|
||||
pub fn original_outputs_contain(&self, node_id: NodeId) -> bool {
|
||||
self.original_outputs().iter().any(|output| output.node_id == node_id)
|
||||
}
|
||||
|
||||
/// Is the node being used directly as a previous output?
|
||||
pub fn previous_outputs_contain(&self, node_id: NodeId) -> Option<bool> {
|
||||
self.previous_outputs.as_ref().map(|outputs| outputs.iter().any(|output| output.node_id == node_id))
|
||||
}
|
||||
|
||||
/// A iterator of all nodes connected by primary inputs.
|
||||
///
|
||||
/// Used for the properties panel and tools.
|
||||
pub fn primary_flow(&self) -> impl Iterator<Item = (&DocumentNode, u64)> {
|
||||
struct FlowIter<'a> {
|
||||
stack: Vec<NodeId>,
|
||||
network: &'a NodeNetwork,
|
||||
}
|
||||
impl<'a> Iterator for FlowIter<'a> {
|
||||
type Item = (&'a DocumentNode, NodeId);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
let node_id = self.stack.pop()?;
|
||||
if let Some(document_node) = self.network.nodes.get(&node_id) {
|
||||
self.stack.extend(
|
||||
document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.take(1) // Only show the primary input
|
||||
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
|
||||
);
|
||||
return Some((document_node, node_id));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
FlowIter {
|
||||
stack: self.outputs.iter().map(|output| output.node_id).collect(),
|
||||
network: &self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -47,6 +47,7 @@ pub enum TaggedValue {
|
||||
Quantization(graphene_core::quantization::QuantizationChannels),
|
||||
OptionalColor(Option<graphene_core::raster::color::Color>),
|
||||
ManipulatorGroupIds(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
||||
VecDVec2(Vec<DVec2>),
|
||||
}
|
||||
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
@@ -104,6 +105,12 @@ impl Hash for TaggedValue {
|
||||
Self::Quantization(quantized_image) => quantized_image.hash(state),
|
||||
Self::OptionalColor(color) => color.hash(state),
|
||||
Self::ManipulatorGroupIds(mirror) => mirror.hash(state),
|
||||
Self::VecDVec2(vec_dvec2) => {
|
||||
vec_dvec2.len().hash(state);
|
||||
for dvec2 in vec_dvec2 {
|
||||
dvec2.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,6 +152,7 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::Quantization(x) => Box::new(x),
|
||||
TaggedValue::OptionalColor(x) => Box::new(x),
|
||||
TaggedValue::ManipulatorGroupIds(x) => Box::new(x),
|
||||
TaggedValue::VecDVec2(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +193,7 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::Quantization(_) => concrete!(graphene_core::quantization::QuantizationChannels),
|
||||
TaggedValue::OptionalColor(_) => concrete!(Option<graphene_core::Color>),
|
||||
TaggedValue::ManipulatorGroupIds(_) => concrete!(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
||||
TaggedValue::VecDVec2(_) => concrete!(Vec<DVec2>),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,6 +403,11 @@ impl TypingContext {
|
||||
self.constructor.get(&node_id).copied()
|
||||
}
|
||||
|
||||
/// Returns the type of a given node id if it exists
|
||||
pub fn type_of(&self, node_id: NodeId) -> Option<&NodeIOTypes> {
|
||||
self.inferred.get(&node_id)
|
||||
}
|
||||
|
||||
/// 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();
|
||||
@@ -416,7 +421,8 @@ impl TypingContext {
|
||||
// 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![]);
|
||||
// TODO: This should return a reference to the value
|
||||
let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![v.ty()]);
|
||||
self.inferred.insert(node_id, types.clone());
|
||||
return Ok(types);
|
||||
}
|
||||
@@ -427,9 +433,9 @@ impl TypingContext {
|
||||
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.input.clone(), node.output.clone()))
|
||||
.map(|node| node.ty())
|
||||
})
|
||||
.collect::<Result<Vec<(Type, Type)>, String>>()?,
|
||||
.collect::<Result<Vec<Type>, String>>()?,
|
||||
};
|
||||
|
||||
// Get the node input type from the proto node declaration
|
||||
@@ -450,26 +456,27 @@ impl TypingContext {
|
||||
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.1, Type::Generic(_))) {
|
||||
if parameters.iter().any(|p| match p {
|
||||
Type::Fn(_, b) if matches!(b.as_ref(), Type::Generic(_)) => true,
|
||||
_ => false,
|
||||
}) {
|
||||
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,
|
||||
};
|
||||
fn covariant(from: &Type, to: &Type) -> bool {
|
||||
match (from, to) {
|
||||
(Type::Concrete(t1), Type::Concrete(t2)) => t1 == t2,
|
||||
(Type::Fn(a1, b1), Type::Fn(a2, b2)) => covariant(a1, a2) && covariant(b1, b2),
|
||||
// TODO: relax this requirement when allowing generic types as inputs
|
||||
(Type::Generic(_), _) => false,
|
||||
(_, Type::Generic(_)) => true,
|
||||
_ => 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.0.clone(), p2.0.clone()) && covariant(p1.1.clone(), p2.1.clone()))
|
||||
})
|
||||
.filter(|node_io| covariant(&input, &node_io.input) && parameters.iter().zip(node_io.parameters.iter()).all(|(p1, p2)| covariant(p1, p2) && covariant(p1, p2)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Attempt to substitute generic types with concrete types and save the list of results
|
||||
@@ -517,7 +524,7 @@ impl TypingContext {
|
||||
|
||||
/// 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().map(|(_, x)| x));
|
||||
let inputs = [&types.input].into_iter().chain(types.parameters.iter().flat_map(|x| x.fn_output()));
|
||||
let mut generics = inputs
|
||||
.filter_map(|t| match t {
|
||||
Type::Generic(out) => Some(out.clone()),
|
||||
@@ -532,15 +539,16 @@ fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
|
||||
}
|
||||
|
||||
/// 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, Type)], generic: &str) -> Result<Type, String> {
|
||||
let inputs = [(&types.input, input)]
|
||||
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result<Type, String> {
|
||||
let inputs = [(Some(&types.input), Some(input))]
|
||||
.into_iter()
|
||||
.chain(types.parameters.iter().map(|(_, x)| x).zip(parameters.iter().map(|(_, x)| x)));
|
||||
let mut concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Type::Generic(input) if generic == input));
|
||||
let (_, out_ty) = concrete_inputs
|
||||
.chain(types.parameters.iter().map(|x| x.fn_output()).zip(parameters.iter().map(|x| x.fn_output())));
|
||||
let concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Some(Type::Generic(input)) if generic == input));
|
||||
let mut outputs = concrete_inputs.flat_map(|(_, out)| out);
|
||||
let out_ty = outputs
|
||||
.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) {
|
||||
if outputs.any(|ty| ty != out_ty) {
|
||||
return Err(format!("Generic output type {generic} is dependent on multiple inputs or parameters",));
|
||||
}
|
||||
Ok(out_ty.clone())
|
||||
|
||||
Reference in New Issue
Block a user