mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 23:38:06 +08:00
merge onto master
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
use crate::node_registry;
|
||||
use crate::node_registry::{MONITOR_NODES, NODE_REGISTRY};
|
||||
use dyn_any::StaticType;
|
||||
use graph_craft::Type;
|
||||
use graph_craft::document::NodeId;
|
||||
use glam::DAffine2;
|
||||
use graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode};
|
||||
use graph_craft::graphene_compiler::Executor;
|
||||
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext};
|
||||
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, downcast_node};
|
||||
use graph_craft::proto::{GraphErrorType, GraphErrors};
|
||||
use graph_craft::{Type, concrete};
|
||||
use graphene_std::application_io::{ExportFormat, RenderConfig, TimingInformation};
|
||||
use graphene_std::memo::{IntrospectMode, MonitorNode};
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI};
|
||||
use graphene_std::{NodeIOTypes, OwnedContextImpl};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
@@ -13,118 +17,116 @@ use std::sync::Arc;
|
||||
/// An executor of a node graph that does not require an online compilation server, and instead uses `Box<dyn ...>`.
|
||||
#[derive(Clone)]
|
||||
pub struct DynamicExecutor {
|
||||
output: NodeId,
|
||||
output: Option<SNI>,
|
||||
/// Stores all of the dynamic node structs.
|
||||
tree: BorrowTree,
|
||||
/// Stores the types of the proto nodes.
|
||||
typing_context: TypingContext,
|
||||
// This allows us to keep the nodes around for one more frame which is used for introspection
|
||||
orphaned_nodes: HashSet<NodeId>,
|
||||
// TODO: Add lifetime for removed nodes so that if a SNI changes, then changes back to its previous SNI, the node does
|
||||
// not have to be reinserted
|
||||
// lifetime: HashSet<(SNI, usize)>,
|
||||
}
|
||||
|
||||
impl Default for DynamicExecutor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
output: Default::default(),
|
||||
output: None,
|
||||
tree: Default::default(),
|
||||
typing_context: TypingContext::new(&node_registry::NODE_REGISTRY),
|
||||
orphaned_nodes: HashSet::new(),
|
||||
typing_context: TypingContext::new(&NODE_REGISTRY, &MONITOR_NODES),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct NodeTypes {
|
||||
pub inputs: Vec<Type>,
|
||||
pub output: Type,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ResolvedDocumentNodeTypes {
|
||||
pub types: HashMap<Vec<NodeId>, NodeTypes>,
|
||||
}
|
||||
|
||||
type Path = Box<[NodeId]>;
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ResolvedDocumentNodeTypesDelta {
|
||||
pub add: Vec<(Path, NodeTypes)>,
|
||||
pub remove: Vec<Path>,
|
||||
}
|
||||
|
||||
impl DynamicExecutor {
|
||||
pub async fn new(proto_network: ProtoNetwork) -> Result<Self, GraphErrors> {
|
||||
let mut typing_context = TypingContext::new(&node_registry::NODE_REGISTRY);
|
||||
pub async fn new(proto_network: Vec<ProtoNode>) -> Result<Self, GraphErrors> {
|
||||
let mut typing_context = TypingContext::default();
|
||||
typing_context.update(&proto_network)?;
|
||||
let output = proto_network.output;
|
||||
let output = proto_network.get(0).map(|protonode| protonode.stable_node_id);
|
||||
let tree = BorrowTree::new(proto_network, &typing_context).await?;
|
||||
|
||||
Ok(Self {
|
||||
tree,
|
||||
output,
|
||||
typing_context,
|
||||
orphaned_nodes: HashSet::new(),
|
||||
})
|
||||
Ok(Self { tree, output, typing_context })
|
||||
}
|
||||
|
||||
/// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible.
|
||||
#[cfg_attr(debug_assertions, inline(never))]
|
||||
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<ResolvedDocumentNodeTypesDelta, GraphErrors> {
|
||||
self.output = proto_network.output;
|
||||
pub async fn update(mut self, proto_network: Vec<ProtoNode>) -> Result<(Vec<(SNI, Vec<Type>)>, Vec<(SNI, usize)>), GraphErrors> {
|
||||
self.output = proto_network.get(0).map(|protonode| protonode.stable_node_id);
|
||||
self.typing_context.update(&proto_network)?;
|
||||
let (add, orphaned) = 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::with_capacity(old_to_remove.len() - self.orphaned_nodes.len().min(old_to_remove.len()));
|
||||
for node_id in old_to_remove {
|
||||
if self.orphaned_nodes.contains(&node_id) {
|
||||
let path = self.tree.free_node(node_id);
|
||||
self.typing_context.remove_inference(node_id);
|
||||
if let Some(path) = path {
|
||||
remove.push(path);
|
||||
}
|
||||
}
|
||||
// A protonode id can change while having the same document path, and the path can change while having the same stable node id.
|
||||
// Either way, the mapping of paths to ids and ids to paths has to be kept in sync.
|
||||
// The mapping of monitor node paths has to kept in sync as well.
|
||||
let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?;
|
||||
let mut remove = Vec::new();
|
||||
for sni in orphaned_proto_nodes {
|
||||
let Some(types) = self.typing_context.type_of(sni) else {
|
||||
log::error!("Could not get type for protonode {sni} when removing");
|
||||
continue;
|
||||
};
|
||||
remove.push((sni, types.inputs.len()));
|
||||
self.tree.free_node(&sni, types.inputs.len());
|
||||
self.typing_context.remove_inference(&sni);
|
||||
}
|
||||
let add = self.document_node_types(add.into_iter()).collect();
|
||||
Ok(ResolvedDocumentNodeTypesDelta { add, remove })
|
||||
|
||||
let add_with_types = add
|
||||
.into_iter()
|
||||
.filter_map(|sni| {
|
||||
let Some(types) = self.typing_context.type_of(sni) else {
|
||||
log::debug!("Could not get type for added node: {sni}");
|
||||
return None;
|
||||
};
|
||||
Some((sni, types.inputs.clone()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok((add_with_types, remove))
|
||||
}
|
||||
|
||||
/// Calls the `Node::serialize` for that specific node, returning for example the cached value for a monitor node. The node path must match the document node path.
|
||||
pub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
|
||||
self.tree.introspect(node_path)
|
||||
/// Intospect the value for that specific protonode input, returning for example the cached value for a monitor node.
|
||||
pub fn introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) -> Result<Box<dyn std::any::Any + Send + Sync>, IntrospectError> {
|
||||
let node = self.get_monitor_node_container(protonode_input)?;
|
||||
node.introspect(introspect_mode).ok_or(IntrospectError::IntrospectNotImplemented)
|
||||
}
|
||||
|
||||
pub fn set_introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) {
|
||||
let Ok(node) = self.get_monitor_node_container(protonode_input) else {
|
||||
log::error!("Could not get monitor node for input: {:?}", protonode_input);
|
||||
return;
|
||||
};
|
||||
node.set_introspect(introspect_mode);
|
||||
}
|
||||
|
||||
pub fn get_monitor_node_container(&self, protonode_input: CompiledProtonodeInput) -> Result<SharedNodeContainer, IntrospectError> {
|
||||
// The SNI of the monitor nodes are the ids of the protonode + input index
|
||||
let monitor_node_id = NodeId(protonode_input.0.0 + protonode_input.1 as u64 + 1);
|
||||
let inserted_node = self.tree.nodes.get(&monitor_node_id).ok_or(IntrospectError::ProtoNodeNotFound(monitor_node_id))?;
|
||||
Ok(inserted_node.clone())
|
||||
}
|
||||
|
||||
pub fn input_type(&self) -> Option<Type> {
|
||||
self.typing_context.type_of(self.output).map(|node_io| node_io.call_argument.clone())
|
||||
self.output.and_then(|output| self.typing_context.type_of(output).map(|node_io| node_io.call_argument.clone()))
|
||||
}
|
||||
|
||||
pub fn tree(&self) -> &BorrowTree {
|
||||
&self.tree
|
||||
}
|
||||
|
||||
pub fn output(&self) -> NodeId {
|
||||
pub fn output(&self) -> Option<SNI> {
|
||||
self.output
|
||||
}
|
||||
|
||||
pub fn output_type(&self) -> Option<Type> {
|
||||
self.typing_context.type_of(self.output).map(|node_io| node_io.return_value.clone())
|
||||
self.output.and_then(|output| self.typing_context.type_of(output).map(|node_io| node_io.return_value.clone()))
|
||||
}
|
||||
|
||||
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())))
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Executor<I, TaggedValue> for &DynamicExecutor
|
||||
where
|
||||
I: StaticType + 'static + Send + Sync + std::panic::UnwindSafe,
|
||||
{
|
||||
fn execute(&self, input: I) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>> {
|
||||
pub fn execute<I>(&self, input: I) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>>
|
||||
where
|
||||
I: dyn_any::StaticType + 'static + Send + Sync + std::panic::UnwindSafe,
|
||||
{
|
||||
Box::pin(async move {
|
||||
use futures::FutureExt;
|
||||
let output_node = self.output.ok_or("Could not execute network before compilation")?;
|
||||
|
||||
let result = self.tree.eval_tagged_value(self.output, input);
|
||||
let result = self.tree.eval_tagged_value(output_node, input);
|
||||
let wrapped_result = std::panic::AssertUnwindSafe(result).catch_unwind().await;
|
||||
|
||||
match wrapped_result {
|
||||
@@ -136,15 +138,98 @@ where
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// If node to evaluate is None then the most downstream node is used
|
||||
// pub async fn evaluate_from_node(&self, editor_context: EditorContext, node_to_evaluate: Option<SNI>) -> Result<TaggedValue, String> {
|
||||
// let node_to_evaluate: NodeId = node_to_evaluate
|
||||
// .or_else(|| self.output)
|
||||
// .ok_or("Could not find output node when evaluating network. Has the network been compiled?")?;
|
||||
// let input_type = self
|
||||
// .typing_context
|
||||
// .type_of(node_to_evaluate)
|
||||
// .map(|node_io| node_io.call_argument.clone())
|
||||
// .ok_or("Could not get input type of network to execute".to_string())?;
|
||||
// let result = match input_type {
|
||||
// t if t == concrete!(EditorContext) => self.execute(editor_context, node_to_evaluate).await.map_err(|e| e.to_string()),
|
||||
// t if t == concrete!(()) => (&self).execute((), node_to_evaluate).await.map_err(|e| e.to_string()),
|
||||
// t => Err(format!("Invalid input type {t:?}")),
|
||||
// };
|
||||
// let result = match result {
|
||||
// Ok(value) => value,
|
||||
// Err(e) => return Err(e),
|
||||
// };
|
||||
|
||||
// Ok(result)
|
||||
// }
|
||||
}
|
||||
pub struct InputMapping {}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EditorContext {
|
||||
// pub footprint: Option<Footprint>,
|
||||
// pub downstream_transform: Option<DAffine2>,
|
||||
// pub real_time: Option<f64>,
|
||||
// pub animation_time: Option<f64>,
|
||||
// pub index: Option<usize>,
|
||||
// pub editor_var_args: Option<(Vec<String>, Vec<Arc<Box<[dyn std::any::Any + 'static + std::panic::UnwindSafe]>>>)>,
|
||||
|
||||
// TODO: Temporarily used to execute with RenderConfig as call argument, will be removed once these fields can be passed
|
||||
// As a scope input to the reworked render node. This will allow the Editor Context to be used to evaluate any node
|
||||
pub render_config: RenderConfig,
|
||||
}
|
||||
|
||||
unsafe impl StaticType for EditorContext {
|
||||
type Static = EditorContext;
|
||||
}
|
||||
|
||||
// impl Default for EditorContext {
|
||||
// fn default() -> Self {
|
||||
// EditorContext {
|
||||
// footprint: None,
|
||||
// downstream_transform: None,
|
||||
// real_time: None,
|
||||
// animation_time: None,
|
||||
// index: None,
|
||||
// // editor_var_args: None,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl EditorContext {
|
||||
// pub fn to_context(&self) -> graphene_std::Context {
|
||||
// let mut context = OwnedContextImpl::default();
|
||||
// if let Some(footprint) = self.footprint {
|
||||
// context.set_footprint(footprint);
|
||||
// }
|
||||
// if let Some(footprint) = self.footprint {
|
||||
// context.set_footprint(footprint);
|
||||
// }
|
||||
// if let Some(downstream_transform) = self.downstream_transform {
|
||||
// context.set_downstream_transform(downstream_transform);
|
||||
// }
|
||||
// if let Some(real_time) = self.real_time {
|
||||
// context.set_real_time(real_time);
|
||||
// }
|
||||
// if let Some(animation_time) = self.animation_time {
|
||||
// context.set_animation_time(animation_time);
|
||||
// }
|
||||
// if let Some(index) = self.index {
|
||||
// context.set_index(index);
|
||||
// }
|
||||
// // if let Some(editor_var_args) = self.editor_var_args {
|
||||
// // let (variable_names, values)
|
||||
// // context.set_varargs((variable_names, values))
|
||||
// // }
|
||||
// context.into_context()
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum IntrospectError {
|
||||
PathNotFound(Vec<NodeId>),
|
||||
ProtoNodeNotFound(NodeId),
|
||||
ProtoNodeNotFound(SNI),
|
||||
NoData,
|
||||
RuntimeNotReady,
|
||||
IntrospectNotImplemented,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for IntrospectError {
|
||||
@@ -154,6 +239,7 @@ impl std::fmt::Display for IntrospectError {
|
||||
IntrospectError::ProtoNodeNotFound(id) => write!(f, "ProtoNode not found: {:?}", id),
|
||||
IntrospectError::NoData => write!(f, "No data found for this node"),
|
||||
IntrospectError::RuntimeNotReady => write!(f, "Node runtime is not ready"),
|
||||
IntrospectError::IntrospectNotImplemented => write!(f, "Intospect not implemented"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,55 +264,41 @@ impl std::fmt::Display for IntrospectError {
|
||||
/// A store of the dynamically typed nodes and also the source map.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct BorrowTree {
|
||||
/// A hashmap of node IDs and dynamically typed nodes.
|
||||
nodes: HashMap<NodeId, (SharedNodeContainer, Path)>,
|
||||
/// A hashmap from the document path to the proto node ID.
|
||||
source_map: HashMap<Path, (NodeId, NodeTypes)>,
|
||||
// A hashmap of node IDs and dynamically typed nodes, as well as the number of inserted monitor nodes
|
||||
nodes: HashMap<SNI, SharedNodeContainer>,
|
||||
}
|
||||
|
||||
impl BorrowTree {
|
||||
pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
|
||||
pub async fn new(proto_network: Vec<ProtoNode>, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
|
||||
let mut nodes = BorrowTree::default();
|
||||
for (id, node) in proto_network.nodes {
|
||||
nodes.push_node(id, node, typing_context).await?
|
||||
for node in proto_network {
|
||||
nodes.push_node(node, typing_context).await?
|
||||
}
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
/// Pushes new nodes into the tree and return orphaned nodes
|
||||
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 new_nodes: 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 {
|
||||
if !self.nodes.contains_key(&id) {
|
||||
new_nodes.push(node.original_location.path.clone().unwrap_or_default().into());
|
||||
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());
|
||||
/// Pushes new nodes into the tree and returns a vec of document nodes that had their types changed, and a vec of all nodes that were removed (including auto inserted value nodes)
|
||||
pub async fn update(&mut self, proto_network: Vec<ProtoNode>, typing_context: &TypingContext) -> Result<(Vec<SNI>, HashSet<SNI>), GraphErrors> {
|
||||
let mut old_nodes = self.nodes.keys().copied().into_iter().collect::<HashSet<_>>();
|
||||
// List of all document node paths that need to be updated, which occurs if their path changes or type changes
|
||||
let mut nodes_with_new_type = Vec::new();
|
||||
for node in proto_network {
|
||||
let sni = node.stable_node_id;
|
||||
old_nodes.remove(&sni);
|
||||
let sni = node.stable_node_id;
|
||||
if !self.nodes.contains_key(&sni) {
|
||||
if node.original_location.send_types_to_editor {
|
||||
nodes_with_new_type.push(sni)
|
||||
}
|
||||
self.push_node(node, typing_context);
|
||||
}
|
||||
old_nodes.remove(&id);
|
||||
}
|
||||
Ok((new_nodes, old_nodes))
|
||||
|
||||
Ok((nodes_with_new_type, old_nodes))
|
||||
}
|
||||
|
||||
fn node_deps(&self, nodes: &[NodeId]) -> Vec<SharedNodeContainer> {
|
||||
nodes.iter().map(|node| self.nodes.get(node).unwrap().0.clone()).collect()
|
||||
}
|
||||
|
||||
fn store_node(&mut self, node: SharedNodeContainer, id: NodeId, path: Path) {
|
||||
self.nodes.insert(id, (node, path));
|
||||
}
|
||||
|
||||
/// Calls the `Node::serialize` for that specific node, returning for example the cached value for a monitor node. The node path must match the document node path.
|
||||
pub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
|
||||
let (id, _) = self.source_map.get(node_path).ok_or_else(|| IntrospectError::PathNotFound(node_path.to_vec()))?;
|
||||
let (node, _path) = self.nodes.get(id).ok_or(IntrospectError::ProtoNodeNotFound(*id))?;
|
||||
node.serialize().ok_or(IntrospectError::NoData)
|
||||
}
|
||||
|
||||
pub fn get(&self, id: NodeId) -> Option<SharedNodeContainer> {
|
||||
self.nodes.get(&id).map(|(node, _)| node.clone())
|
||||
fn node_deps(&self, nodes: &[SNI]) -> Vec<SharedNodeContainer> {
|
||||
nodes.iter().map(|node| self.nodes.get(node).unwrap().clone()).collect()
|
||||
}
|
||||
|
||||
/// Evaluate the output node of the [`BorrowTree`].
|
||||
@@ -235,18 +307,18 @@ impl BorrowTree {
|
||||
I: StaticType + 'i + Send + Sync,
|
||||
O: StaticType + 'i,
|
||||
{
|
||||
let (node, _path) = self.nodes.get(&id).cloned()?;
|
||||
let node = self.nodes.get(&id).cloned()?;
|
||||
let output = node.eval(Box::new(input));
|
||||
dyn_any::downcast::<O>(output.await).ok().map(|o| *o)
|
||||
}
|
||||
/// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value.
|
||||
/// This ensures that no borrowed data can escape the node graph.
|
||||
pub async fn eval_tagged_value<I>(&self, id: NodeId, input: I) -> Result<TaggedValue, String>
|
||||
pub async fn eval_tagged_value<I>(&self, id: SNI, input: I) -> Result<TaggedValue, String>
|
||||
where
|
||||
I: StaticType + 'static + Send + Sync,
|
||||
{
|
||||
let (node, _path) = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
|
||||
let output = node.eval(Box::new(input));
|
||||
let inserted_node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
|
||||
let output = inserted_node.eval(Box::new(input));
|
||||
TaggedValue::try_from_any(output.await)
|
||||
}
|
||||
|
||||
@@ -305,58 +377,12 @@ impl BorrowTree {
|
||||
/// - Removes the node from `nodes` HashMap.
|
||||
/// - If the node is the primary node for its path in the `source_map`, it's also removed from there.
|
||||
/// - Returns `None` if the node is not found in the `nodes` HashMap.
|
||||
pub fn free_node(&mut self, id: NodeId) -> Option<Path> {
|
||||
let (_, path) = self.nodes.remove(&id)?;
|
||||
if self.source_map.get(&path)?.0 == id {
|
||||
self.source_map.remove(&path);
|
||||
return Some(path);
|
||||
pub fn free_node(&mut self, id: &SNI, inputs: usize) {
|
||||
self.nodes.remove(&id);
|
||||
// Also remove all corresponding monitor nodes
|
||||
for monitor_index in 1..=inputs {
|
||||
self.nodes.remove(&NodeId(id.0 + monitor_index as u64));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Updates the source map for a given node in the [`BorrowTree`].
|
||||
///
|
||||
/// This method updates or inserts an entry in the `source_map` HashMap for the specified node,
|
||||
/// using type information from the provided [`TypingContext`] and [`ProtoNode`].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `self` - Mutable reference to the [`BorrowTree`].
|
||||
/// * `id` - The `NodeId` of the node to update in the source map.
|
||||
/// * `typing_context` - A reference to the [`TypingContext`] containing type information.
|
||||
/// * `proto_node` - A reference to the [`ProtoNode`] containing original location information.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `bool` - `true` if a new entry was inserted, `false` if an existing entry was updated.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// - Updates or inserts an entry in the `source_map` HashMap.
|
||||
/// - Uses the `ProtoNode`'s original location path as the key for the source map.
|
||||
/// - 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.
|
||||
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 {
|
||||
log::warn!("did not find type");
|
||||
return false;
|
||||
};
|
||||
let inputs = [&node_io.call_argument].into_iter().chain(&node_io.inputs).cloned().collect();
|
||||
|
||||
let node_path = &proto_node.original_location.path.as_ref().unwrap_or(const { &vec![] });
|
||||
|
||||
let entry = self.source_map.entry(node_path.to_vec().into()).or_default();
|
||||
|
||||
let update = (
|
||||
id,
|
||||
NodeTypes {
|
||||
inputs,
|
||||
output: node_io.return_value.clone(),
|
||||
},
|
||||
);
|
||||
let modified = *entry != update;
|
||||
*entry = update;
|
||||
modified
|
||||
}
|
||||
|
||||
/// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`.
|
||||
@@ -374,53 +400,58 @@ impl BorrowTree {
|
||||
/// - `Nodes`: Constructs a node using other nodes as dependencies.
|
||||
/// - Uses the constructor function from the `typing_context` for `Nodes` construction arguments.
|
||||
/// - Returns an error if no constructor is found for the given node ID.
|
||||
async fn push_node(&mut self, id: NodeId, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> {
|
||||
self.update_source_map(id, typing_context, &proto_node);
|
||||
let path = proto_node.original_location.path.clone().unwrap_or_default();
|
||||
|
||||
match &proto_node.construction_args {
|
||||
async fn push_node(&mut self, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> {
|
||||
let sni = proto_node.stable_node_id;
|
||||
// Move the value into the upcast node instead of cloning it
|
||||
match proto_node.construction_args {
|
||||
ConstructionArgs::Value(value) => {
|
||||
let node = if let TaggedValue::EditorApi(api) = &**value {
|
||||
let editor_api = UpcastAsRefNode::new(api.clone());
|
||||
let node = Box::new(editor_api) as TypeErasedBox<'_>;
|
||||
NodeContainer::new(node)
|
||||
} else {
|
||||
let upcasted = UpcastNode::new(value.to_owned());
|
||||
let node = Box::new(upcasted) as TypeErasedBox<'_>;
|
||||
NodeContainer::new(node)
|
||||
};
|
||||
self.store_node(node, id, path.into());
|
||||
// The constructor for nodes with value construction args (value nodes) is not called.
|
||||
// It is not necessary to clone the Arc for the wasm editor api, since the value node is deduplicated and only called once.
|
||||
// It is cloned whenever it is evaluated
|
||||
let upcasted = UpcastNode::new(value);
|
||||
let node = Box::new(upcasted) as TypeErasedBox<'_>;
|
||||
self.nodes.insert(sni, NodeContainer::new(node));
|
||||
}
|
||||
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
|
||||
ConstructionArgs::Nodes(ids) => {
|
||||
let ids: Vec<_> = ids.iter().map(|(id, _)| *id).collect();
|
||||
let construction_nodes = self.node_deps(&ids);
|
||||
let constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||
let node = constructor(construction_nodes).await;
|
||||
ConstructionArgs::Nodes(ref node_construction_args) => {
|
||||
let construction_nodes = self.node_deps(&node_construction_args.inputs);
|
||||
|
||||
let types = typing_context.type_of(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||
let monitor_nodes = construction_nodes
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(input_index, construction_node)| {
|
||||
let input_type = types.inputs.get(input_index).unwrap(); //.ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||
let monitor_constructor = typing_context.monitor_constructor(input_type).unwrap(); // .ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||
let monitor = monitor_constructor(construction_node);
|
||||
let monitor_node_container = NodeContainer::new(monitor);
|
||||
self.nodes.insert(NodeId(sni.0 + input_index as u64 + 1), monitor_node_container.clone());
|
||||
monitor_node_container
|
||||
})
|
||||
.collect();
|
||||
|
||||
let constructor = typing_context.constructor(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||
let node = constructor(monitor_nodes).await;
|
||||
let node = NodeContainer::new(node);
|
||||
self.store_node(node, id, path.into());
|
||||
self.nodes.insert(sni, node);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the source map of the borrow tree
|
||||
pub fn source_map(&self) -> &HashMap<Path, (NodeId, NodeTypes)> {
|
||||
&self.source_map
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::uuid::NodeId;
|
||||
|
||||
#[test]
|
||||
fn push_node_sync() {
|
||||
let mut tree = BorrowTree::default();
|
||||
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![]);
|
||||
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![], NodeId(0));
|
||||
let context = TypingContext::default();
|
||||
let future = tree.push_node(NodeId(0), val_1_protonode, &context);
|
||||
let future = tree.push_node(val_1_protonode, &context);
|
||||
futures::executor::block_on(future).unwrap();
|
||||
let _node = tree.get(NodeId(0)).unwrap();
|
||||
let result = futures::executor::block_on(tree.eval(NodeId(0), ()));
|
||||
|
||||
Reference in New Issue
Block a user