mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 03:58:11 +08:00
Improve resolved types
This commit is contained in:
@@ -7,6 +7,7 @@ pub struct Compiler {}
|
|||||||
impl Compiler {
|
impl Compiler {
|
||||||
pub fn compile(&self, mut network: NodeNetwork) -> impl Iterator<Item = Result<ProtoNetwork, String>> {
|
pub fn compile(&self, mut network: NodeNetwork) -> impl Iterator<Item = Result<ProtoNetwork, String>> {
|
||||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||||
|
network.generate_node_paths(&[]);
|
||||||
network.populate_dependants();
|
network.populate_dependants();
|
||||||
for id in node_ids {
|
for id in node_ids {
|
||||||
network.flatten(id);
|
network.flatten(id);
|
||||||
@@ -19,6 +20,9 @@ impl Compiler {
|
|||||||
proto_networks.map(move |mut proto_network| {
|
proto_networks.map(move |mut proto_network| {
|
||||||
proto_network.resolve_inputs()?;
|
proto_network.resolve_inputs()?;
|
||||||
proto_network.generate_stable_node_ids();
|
proto_network.generate_stable_node_ids();
|
||||||
|
// Deduplication of identical protonodes is more efficient, but also means the type information is lost for the corresponding document node
|
||||||
|
// let mut seen = std::collections::HashSet::new();
|
||||||
|
// proto_network.nodes.retain(|(protonode_id, _)| seen.insert(*protonode_id));
|
||||||
Ok(proto_network)
|
Ok(proto_network)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,6 +193,11 @@ impl ProtoNode {
|
|||||||
|
|
||||||
self.identifier.name.hash(&mut hasher);
|
self.identifier.name.hash(&mut hasher);
|
||||||
self.construction_args.hash(&mut hasher);
|
self.construction_args.hash(&mut hasher);
|
||||||
|
// TODO: This is necessary since a mapping of path to type has to be stored for document node
|
||||||
|
// If this is removed, the identical node would be removed and the mapping wouldn't be added
|
||||||
|
// This can be improved by storing a list of paths for each proto node.
|
||||||
|
self.original_location.path.hash(&mut hasher);
|
||||||
|
|
||||||
if self.skip_deduplication {
|
if self.skip_deduplication {
|
||||||
self.original_location.path.hash(&mut hasher);
|
self.original_location.path.hash(&mut hasher);
|
||||||
}
|
}
|
||||||
@@ -210,25 +215,6 @@ impl ProtoNode {
|
|||||||
Some(NodeId(hasher.finish()))
|
Some(NodeId(hasher.finish()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a new [`ProtoNode`] with the specified construction args and a `ClonedNode` implementation.
|
|
||||||
pub fn value(value: ConstructionArgs, path: Vec<NodeId>) -> Self {
|
|
||||||
let inputs_exposed = match &value {
|
|
||||||
ConstructionArgs::Nodes(nodes) => nodes.len() + 1,
|
|
||||||
_ => 2,
|
|
||||||
};
|
|
||||||
Self {
|
|
||||||
identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"),
|
|
||||||
construction_args: value,
|
|
||||||
input: ProtoNodeInput::ManualComposition(concrete!(Context)),
|
|
||||||
original_location: OriginalLocation {
|
|
||||||
path: Some(path),
|
|
||||||
inputs_exposed: vec![false; inputs_exposed],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
skip_deduplication: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts all references to other node IDs into new IDs by running the specified function on them.
|
/// Converts all references to other node IDs into new IDs by running the specified function on them.
|
||||||
/// This can be used when changing the IDs of the nodes, for example in the case of generating stable IDs.
|
/// This can be used when changing the IDs of the nodes, for example in the case of generating stable IDs.
|
||||||
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId, skip_lambdas: bool) {
|
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId, skip_lambdas: bool) {
|
||||||
@@ -319,29 +305,6 @@ impl ProtoNetwork {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove
|
|
||||||
/// Create a hashmap with the list of nodes this proto network depends on/uses as inputs.
|
|
||||||
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 {
|
|
||||||
match &node.input {
|
|
||||||
ProtoNodeInput::Node(ref_id) | ProtoNodeInput::NodeLambda(ref_id) => {
|
|
||||||
self.check_ref(ref_id, id);
|
|
||||||
edges.entry(*id).or_default().push(*ref_id)
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
|
|
||||||
for (ref_id, _) in ref_nodes {
|
|
||||||
self.check_ref(ref_id, id);
|
|
||||||
edges.entry(*id).or_default().push(*ref_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edges
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_inwards_edges_with_mapping(&self) -> (Vec<Vec<usize>>, FxHashMap<NodeId, usize>) {
|
fn collect_inwards_edges_with_mapping(&self) -> (Vec<Vec<usize>>, FxHashMap<NodeId, usize>) {
|
||||||
let id_map: FxHashMap<_, _> = self.nodes.iter().enumerate().map(|(idx, (id, _))| (*id, idx)).collect();
|
let id_map: FxHashMap<_, _> = self.nodes.iter().enumerate().map(|(idx, (id, _))| (*id, idx)).collect();
|
||||||
|
|
||||||
@@ -477,9 +440,22 @@ impl ProtoNetwork {
|
|||||||
fn is_topologically_sorted(&self) -> bool {
|
fn is_topologically_sorted(&self) -> bool {
|
||||||
let mut visited = HashSet::new();
|
let mut visited = HashSet::new();
|
||||||
|
|
||||||
let inwards_edges = self.collect_inwards_edges();
|
for (id, node) in &self.nodes {
|
||||||
for (id, _) in &self.nodes {
|
let mut upstream_nodes = Vec::new();
|
||||||
for &dependency in inwards_edges.get(id).unwrap_or(&Vec::new()) {
|
match &node.input {
|
||||||
|
ProtoNodeInput::Node(ref_id) | ProtoNodeInput::NodeLambda(ref_id) => {
|
||||||
|
upstream_nodes.push(*ref_id);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
|
||||||
|
for (ref_id, _) in ref_nodes {
|
||||||
|
upstream_nodes.push(*ref_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for dependency in upstream_nodes {
|
||||||
if !visited.contains(&dependency) {
|
if !visited.contains(&dependency) {
|
||||||
dbg!(id, dependency);
|
dbg!(id, dependency);
|
||||||
dbg!(&visited);
|
dbg!(&visited);
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ pub struct DynamicExecutor {
|
|||||||
tree: BorrowTree,
|
tree: BorrowTree,
|
||||||
/// Stores the types of the proto nodes.
|
/// Stores the types of the proto nodes.
|
||||||
typing_context: TypingContext,
|
typing_context: TypingContext,
|
||||||
// This allows us to keep the nodes around for one more frame which is used for introspection
|
|
||||||
orphaned_nodes: HashSet<NodeId>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DynamicExecutor {
|
impl Default for DynamicExecutor {
|
||||||
@@ -28,7 +26,6 @@ impl Default for DynamicExecutor {
|
|||||||
output: Default::default(),
|
output: Default::default(),
|
||||||
tree: Default::default(),
|
tree: Default::default(),
|
||||||
typing_context: TypingContext::new(&node_registry::NODE_REGISTRY),
|
typing_context: TypingContext::new(&node_registry::NODE_REGISTRY),
|
||||||
orphaned_nodes: HashSet::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,12 +56,7 @@ impl DynamicExecutor {
|
|||||||
let output = proto_network.output;
|
let output = proto_network.output;
|
||||||
let tree = BorrowTree::new(proto_network, &typing_context).await?;
|
let tree = BorrowTree::new(proto_network, &typing_context).await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self { tree, output, typing_context })
|
||||||
tree,
|
|
||||||
output,
|
|
||||||
typing_context,
|
|
||||||
orphaned_nodes: HashSet::new(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible.
|
/// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible.
|
||||||
@@ -72,16 +64,13 @@ impl DynamicExecutor {
|
|||||||
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<ResolvedDocumentNodeTypesDelta, GraphErrors> {
|
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<ResolvedDocumentNodeTypesDelta, GraphErrors> {
|
||||||
self.output = proto_network.output;
|
self.output = proto_network.output;
|
||||||
self.typing_context.update(&proto_network)?;
|
self.typing_context.update(&proto_network)?;
|
||||||
let (add, orphaned) = self.tree.update(proto_network, &self.typing_context).await?;
|
let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?;
|
||||||
let old_to_remove = core::mem::replace(&mut self.orphaned_nodes, orphaned);
|
let mut remove = Vec::new();
|
||||||
let mut remove = Vec::with_capacity(old_to_remove.len() - self.orphaned_nodes.len().min(old_to_remove.len()));
|
for node_id in orphaned_proto_nodes {
|
||||||
for node_id in old_to_remove {
|
let path = self.tree.free_node(node_id);
|
||||||
if self.orphaned_nodes.contains(&node_id) {
|
self.typing_context.remove_inference(node_id);
|
||||||
let path = self.tree.free_node(node_id);
|
if let Some(path) = path {
|
||||||
self.typing_context.remove_inference(node_id);
|
remove.push(path);
|
||||||
if let Some(path) = path {
|
|
||||||
remove.push(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let add = self.document_node_types(add.into_iter()).collect();
|
let add = self.document_node_types(add.into_iter()).collect();
|
||||||
@@ -111,8 +100,6 @@ impl DynamicExecutor {
|
|||||||
|
|
||||||
pub fn document_node_types<'a>(&'a self, nodes: impl Iterator<Item = Path> + 'a) -> impl Iterator<Item = (Path, NodeTypes)> + 'a {
|
pub fn document_node_types<'a>(&'a self, nodes: impl Iterator<Item = Path> + 'a) -> impl Iterator<Item = (Path, NodeTypes)> + 'a {
|
||||||
nodes.flat_map(|id| self.tree.source_map().get(&id).map(|(_, b)| (id, b.clone())))
|
nodes.flat_map(|id| self.tree.source_map().get(&id).map(|(_, b)| (id, b.clone())))
|
||||||
// TODO: https://github.com/GraphiteEditor/Graphite/issues/1767
|
|
||||||
// TODO: Non exposed inputs are not added to the inputs_source_map, so they are not included in the resolved_document_node_types. The type is still available in the typing_context. This only affects the UI-only "Import" node.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,21 +180,28 @@ impl BorrowTree {
|
|||||||
Ok(nodes)
|
Ok(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes new nodes into the tree and return orphaned nodes
|
/// Pushes new nodes into the tree and returns a vec of nodes that had their types changed, and a vec of nodes that were removed
|
||||||
|
/// Does not return cloned nodes in the vec of added nodes, since their types are not needed by the editor
|
||||||
pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec<Path>, HashSet<NodeId>), GraphErrors> {
|
pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec<Path>, HashSet<NodeId>), GraphErrors> {
|
||||||
let mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect();
|
let mut old_nodes = self.nodes.keys().copied().collect::<HashSet<_>>();
|
||||||
let mut new_nodes: Vec<_> = Vec::new();
|
let mut nodes_with_new_type: Vec<_> = Vec::new();
|
||||||
// TODO: Problem: When an identity node is connected directly to an export the first input to identity node is not added to the proto network, while the second input is. This means the primary input does not have a type.
|
|
||||||
for (id, node) in proto_network.nodes {
|
for (id, node) in proto_network.nodes {
|
||||||
|
let is_cloned_node = node.identifier == "graphene_core::value::ClonedNode".into();
|
||||||
|
let node_path = node.original_location.path.clone().unwrap_or_default().into();
|
||||||
|
// A document node is either added, or changed so that a new stable node id was regenerated
|
||||||
if !self.nodes.contains_key(&id) {
|
if !self.nodes.contains_key(&id) {
|
||||||
new_nodes.push(node.original_location.path.clone().unwrap_or_default().into());
|
// If the path changes, then the id must change as well, which means this will update the mapping
|
||||||
|
// Returns whether the type changed for the document node path
|
||||||
|
if !is_cloned_node && self.update_source_map(id, typing_context, &node) {
|
||||||
|
nodes_with_new_type.push(node_path);
|
||||||
|
}
|
||||||
self.push_node(id, node, typing_context).await?;
|
self.push_node(id, node, typing_context).await?;
|
||||||
} else if self.update_source_map(id, typing_context, &node) {
|
|
||||||
new_nodes.push(node.original_location.path.clone().unwrap_or_default().into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
old_nodes.remove(&id);
|
old_nodes.remove(&id);
|
||||||
}
|
}
|
||||||
Ok((new_nodes, old_nodes))
|
|
||||||
|
Ok((nodes_with_new_type, old_nodes))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_deps(&self, nodes: &[NodeId]) -> Vec<SharedNodeContainer> {
|
fn node_deps(&self, nodes: &[NodeId]) -> Vec<SharedNodeContainer> {
|
||||||
@@ -308,9 +302,13 @@ impl BorrowTree {
|
|||||||
pub fn free_node(&mut self, id: NodeId) -> Option<Path> {
|
pub fn free_node(&mut self, id: NodeId) -> Option<Path> {
|
||||||
let (_, path) = self.nodes.remove(&id)?;
|
let (_, path) = self.nodes.remove(&id)?;
|
||||||
if self.source_map.get(&path)?.0 == id {
|
if self.source_map.get(&path)?.0 == id {
|
||||||
|
// This occurs if the protonode got removed, and a new protonode with the same path was not created.
|
||||||
|
// This occurs when the document node got deleted.
|
||||||
self.source_map.remove(&path);
|
self.source_map.remove(&path);
|
||||||
return Some(path);
|
return Some(path);
|
||||||
}
|
}
|
||||||
|
// The proto node id changed for a document node, but the source map was already updated
|
||||||
|
// This occurs when an input changes for example
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +333,7 @@ impl BorrowTree {
|
|||||||
/// - Updates or inserts an entry in the `source_map` HashMap.
|
/// - Updates or inserts an entry in the `source_map` HashMap.
|
||||||
/// - Uses the `ProtoNode`'s original location path as the key for the source map.
|
/// - Uses the `ProtoNode`'s original location path as the key for the source map.
|
||||||
/// - Collects input types from both the main input and parameters.
|
/// - Collects input types from both the main input and parameters.
|
||||||
/// - Returns `false` and logs a warning if the node's type information is not found in the typing context.
|
/// - Returns `false` if the old type is the same as the new type
|
||||||
fn update_source_map(&mut self, id: NodeId, typing_context: &TypingContext, proto_node: &ProtoNode) -> bool {
|
fn update_source_map(&mut self, id: NodeId, typing_context: &TypingContext, proto_node: &ProtoNode) -> bool {
|
||||||
let Some(node_io) = typing_context.type_of(id) else {
|
let Some(node_io) = typing_context.type_of(id) else {
|
||||||
log::warn!("did not find type");
|
log::warn!("did not find type");
|
||||||
@@ -354,9 +352,10 @@ impl BorrowTree {
|
|||||||
output: node_io.return_value.clone(),
|
output: node_io.return_value.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let modified = *entry != update;
|
|
||||||
|
let type_changed = entry.1 != update.1;
|
||||||
*entry = update;
|
*entry = update;
|
||||||
modified
|
return type_changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`.
|
/// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`.
|
||||||
@@ -418,7 +417,12 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn push_node_sync() {
|
fn push_node_sync() {
|
||||||
let mut tree = BorrowTree::default();
|
let mut tree = BorrowTree::default();
|
||||||
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![]);
|
let val_1_protonode = ProtoNode {
|
||||||
|
identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"),
|
||||||
|
construction_args: ConstructionArgs::Value(TaggedValue::U32(2u32).into()),
|
||||||
|
input: ProtoNodeInput::ManualComposition(concrete!(Context)),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let context = TypingContext::default();
|
let context = TypingContext::default();
|
||||||
let future = tree.push_node(NodeId(0), val_1_protonode, &context);
|
let future = tree.push_node(NodeId(0), val_1_protonode, &context);
|
||||||
futures::executor::block_on(future).unwrap();
|
futures::executor::block_on(future).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user