Simplify compilation

This commit is contained in:
Adam
2025-07-12 01:53:41 -07:00
parent f5c6b65fcc
commit 8b665d158c
24 changed files with 863 additions and 1012 deletions

View File

@@ -51,26 +51,52 @@ pub trait ExtractAll: ExtractFootprint + ExtractDownstreamTransform + ExtractInd
impl<T: ?Sized + ExtractFootprint + ExtractDownstreamTransform + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {}
#[derive(Debug, Clone, PartialEq)]
#[repr(u8)]
pub enum ContextDependency {
ExtractFootprint,
ExtractFootprint = 0b10000000,
// Can be used by cull nodes to check if the final output would be outside the footprint viewport
ExtractDownstreamTransform,
ExtractRealTime,
ExtractAnimationTime,
ExtractIndex,
ExtractVarArgs,
ExtractDownstreamTransform = 0b01000000,
ExtractRealTime = 0b00100000,
ExtractAnimationTime = 0b00010000,
ExtractIndex = 0b00001000,
ExtractVarArgs = 0b00000100,
}
pub fn all_context_dependencies() -> Vec<ContextDependency> {
vec![
ContextDependency::ExtractFootprint,
// Can be used by cull nodes to check if the final output would be outside the footprint viewport
ContextDependency::ExtractDownstreamTransform,
ContextDependency::ExtractRealTime,
ContextDependency::ExtractAnimationTime,
ContextDependency::ExtractIndex,
ContextDependency::ExtractVarArgs,
]
#[derive(Debug, Clone, PartialEq)]
pub struct ContextDependencies(pub u8);
impl ContextDependencies {
pub fn all_context_dependencies() -> Self {
ContextDependencies(0b11111100)
}
pub fn none() -> Self {
ContextDependencies(0b00000000)
}
pub fn is_empty(&self) -> bool {
self.0 & Self::all_context_dependencies().0 == 0
}
pub fn from(dependencies: Vec<ContextDependency>) -> Self {
let mut new = Self::none();
for dependency in dependencies {
new.0 |= dependency as u8
}
new
}
pub fn inverse(self) -> Self {
Self(!self.0)
}
pub fn add_dependencies(&mut self, other: &Self) {
self.0 |= other.0
}
pub fn difference(&mut self, other: &Self) {
self.0 = (!self.0) & other.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -348,19 +374,25 @@ impl OwnedContextImpl {
}
}
pub fn nullify(&mut self, nullify: &Vec<ContextDependency>) {
for context_dependency in nullify {
match context_dependency {
ContextDependency::ExtractFootprint => self.footprint = None,
ContextDependency::ExtractDownstreamTransform => self.downstream_transform = None,
ContextDependency::ExtractRealTime => self.real_time = None,
ContextDependency::ExtractAnimationTime => self.animation_time = None,
ContextDependency::ExtractIndex => self.index = None,
ContextDependency::ExtractVarArgs => {
self.varargs = None;
self.parent = None
}
}
pub fn nullify(&mut self, nullify: &ContextDependencies) {
if nullify.0 & (ContextDependency::ExtractFootprint as u8) != 0 {
self.footprint = None;
}
if nullify.0 & (ContextDependency::ExtractDownstreamTransform as u8) != 0 {
self.downstream_transform = None;
}
if nullify.0 & (ContextDependency::ExtractRealTime as u8) != 0 {
self.real_time = None;
}
if nullify.0 & (ContextDependency::ExtractAnimationTime as u8) != 0 {
self.animation_time = None;
}
if nullify.0 & (ContextDependency::ExtractIndex as u8) != 0 {
self.index = None;
}
if nullify.0 & (ContextDependency::ExtractVarArgs as u8) != 0 {
self.varargs = None;
self.parent = None
}
}
}

View File

@@ -60,17 +60,11 @@ pub trait Node<'i, Input> {
std::any::type_name::<Self>()
}
/// Get the call argument or output data for the monitor node on the next evaluation after set_introspect_input
/// Also returns a boolean of whether the node was evaluated
fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
// If check if evaluated is true, then it returns None if the node has not been evaluated since the last introspection
fn introspect(&self, _check_if_evaluated: bool) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
log::warn!("Node::introspect not implemented for {}", std::any::type_name::<Self>());
None
}
// The introspect mode is set before the graph evaluation, and tells the monitor node what data to store
fn set_introspect(&self, _introspect_mode: IntrospectMode) {
log::warn!("Node::set_introspect not implemented for {}", std::any::type_name::<Self>());
}
}
mod types;

View File

@@ -12,7 +12,6 @@ use std::sync::Mutex;
pub struct MonitorMemoNode<T, CachedNode> {
// Introspection cache, uses the hash of the nullified context with default var args
// cache: Arc<Mutex<std::collections::HashMap<u64, Arc<T>>>>,
// Return value cache,
cache: Arc<Mutex<Option<(u64, Arc<T>)>>>,
node: CachedNode,
changed_since_last_eval: Arc<Mutex<bool>>,
@@ -25,31 +24,7 @@ where
// TODO: This should return a reference to the cached cached_value
// but that requires a lot of lifetime magic <- This was suggested by copilot but is pretty accurate xD
type Output = DynFuture<'i, T>;
// fn eval(&'i self, input: I) -> Self::Output {
// let mut hasher = DefaultHasher::new();
// input.hash(&mut hasher);
// let hash = hasher.finish();
// if let Some(data) = self.cache.lock().unwrap().get(&hash) {
// let cloned_data = (**data).clone();
// Box::pin(async move { cloned_data })
// } else {
// let fut = self.node.eval(input);
// let cache = self.cache.clone();
// Box::pin(async move {
// let value = fut.await;
// cache.lock().unwrap().insert(hash, Arc::new(value.clone()));
// value
// })
// }
// }
// fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
// let mut hasher = DefaultHasher::new();
// OwnedContextImpl::default().into_context().hash(&mut hasher);
// let hash = hasher.finish();
// self.cache.lock().unwrap().get(&hash).map(|data| (*data).clone() as Arc<dyn std::any::Any + Send + Sync>)
// }
fn eval(&'i self, input: I) -> Self::Output {
let mut hasher = DefaultHasher::new();
input.hash(&mut hasher);
@@ -69,13 +44,20 @@ where
})
}
}
fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
if *self.changed_since_last_eval.lock().unwrap() {
*self.changed_since_last_eval.lock().unwrap() = false;
Some(self.cache.lock().unwrap().as_ref().expect("Cached data should always be evaluated before introspection").1.clone() as Arc<dyn std::any::Any + Send + Sync>)
} else {
None
// TODO: Consider returning a reference to the entire cache so the frontend reference is automatically updated as the context changes
fn introspect(&self, check_if_evaluated: bool) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
let mut changed = self.changed_since_last_eval.lock().unwrap();
if check_if_evaluated {
if !*changed {
return None;
}
}
*changed = false;
let cache_guard = self.cache.lock().unwrap();
let cached = cache_guard.as_ref().expect("Cached data should always be evaluated before introspection");
Some(cached.1.clone() as Arc<dyn std::any::Any + Send + Sync>)
}
}
@@ -230,21 +212,6 @@ where
output
})
}
// After introspecting, the input/output get set to None because the Arc is moved to the editor where it can be directly accessed.
fn introspect(&self, introspect_mode: IntrospectMode) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
match introspect_mode {
IntrospectMode::Input => self.input.lock().unwrap().take().map(|input| input as Arc<dyn std::any::Any + Send + Sync>),
IntrospectMode::Data => self.output.lock().unwrap().take().map(|output| output as Arc<dyn std::any::Any + Send + Sync>),
}
}
fn set_introspect(&self, introspect_mode: IntrospectMode) {
match introspect_mode {
IntrospectMode::Input => *self.introspect_input.lock().unwrap() = true,
IntrospectMode::Data => *self.introspect_output.lock().unwrap() = true,
}
}
}
impl<I, O, N> MonitorNode<I, O, N> {

View File

@@ -1,4 +1,4 @@
use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
use crate::{ContextDependencies, Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
use dyn_any::{DynAny, StaticType};
use std::borrow::Cow;
use std::collections::HashMap;
@@ -109,7 +109,7 @@ pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::ne
pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
pub static NODE_CONTEXT_DEPENDENCY: LazyLock<Mutex<HashMap<String, Vec<crate::ContextDependency>>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
pub static NODE_CONTEXT_DEPENDENCY: LazyLock<Mutex<HashMap<String, ContextDependencies>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
#[cfg(not(target_arch = "wasm32"))]
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;
@@ -290,12 +290,8 @@ where
}
}
fn introspect(&self, introspect_mode: crate::IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
self.node.introspect(introspect_mode)
}
fn set_introspect(&self, introspect_mode: crate::IntrospectMode) {
self.node.set_introspect(introspect_mode);
fn introspect(&self, check_if_evaluated: bool) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
self.node.introspect(check_if_evaluated)
}
fn reset(&self) {

View File

@@ -1,14 +1,14 @@
pub mod value;
use crate::document::value::TaggedValue;
use crate::proto::{ConstructionArgs, NodeConstructionArgs, NodeValueArgs, ProtoNetwork, ProtoNode, UpstreamInputMetadata};
use crate::proto::{ConstructionArgs, NodeConstructionArgs, OriginalLocation, ProtoNetwork, ProtoNode, UpstreamInputMetadata};
use dyn_any::DynAny;
use glam::IVec2;
use graphene_core::memo::MemoHashGuard;
use graphene_core::registry::NODE_CONTEXT_DEPENDENCY;
pub use graphene_core::uuid::generate_uuid;
use graphene_core::uuid::{CompiledProtonodeInput, NodeId, ProtonodePath, SNI};
use graphene_core::{Context, Cow, MemoHash, ProtoNodeIdentifier, Type};
use graphene_core::uuid::{NodeId, ProtonodePath, SNI};
use graphene_core::{Context, ContextDependencies, Cow, MemoHash, NodeIOTypes, ProtoNodeIdentifier, Type};
use rustc_hash::FxHashMap;
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
@@ -169,6 +169,13 @@ impl NodeInput {
_ => false,
}
}
pub fn is_wire(&self) -> bool {
match self {
NodeInput::Node { .. } | NodeInput::Network { .. } => true,
_ => false,
}
}
}
#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]
@@ -510,169 +517,96 @@ impl NodeNetwork {
/// Functions for compiling the network
impl NodeNetwork {
// Returns a topologically sorted vec of vec of protonodes, as well as metadata extracted during compilation
// The first index represents the greatest distance to the export
// Compiles a network with one export where any scope injections are added the top level network, and the network to run is implemented as a DocumentNodeImplementation::Network
// The traversal input is the node which calls the network to be flattened. If it is None, then start from the export.
// Every value protonode stores the connector which directly called it, which is used to map the value input to the protonode caller.
// Every value input connector is mapped to its caller, and every protonode is mapped to its caller. If there are multiple, then they are compared to ensure it is the same between compilations
pub fn flatten(
&mut self,
) -> Result<
(
ProtoNetwork,
Vec<(Vec<AbsoluteInputConnector>, CompiledProtonodeInput)>,
Vec<(Vec<ProtonodePath>, CompiledProtonodeInput)>,
),
String,
> {
// Returns a topologically sorted vec of protonodes, as well as metadata extracted during compilation
pub fn flatten(&mut self) -> Result<(ProtoNetwork, Vec<(OriginalLocation, SNI)>), String> {
// These three arrays are stored in parallel
let mut protonetwork = Vec::new();
// This function creates a topologically flattened network with populated original location fields but unmapped inputs
// The input to flattened protonode hashmap is used to map the inputs
// The protonode indices maps the node path to its index, used to map the caller inputs of any node to the new SNI
let mut protonode_indices = HashMap::new();
self.traverse_input(&mut protonetwork, &mut HashMap::new(), &mut protonode_indices, AbsoluteInputConnector::traversal_start(), None);
// If a node with the same sni is reached, then its original location metadata must be added to the one at the higher vec index
// The index will always be a ProtonodeEntry::Protonode
let mut generated_snis_to_index = HashMap::new();
// Generate SNI's. This gets called after all node inputs are replaced with their indices
for protonode_index in 0..protonetwork.len() {
let ProtonodeEntry::Protonode(protonode) = protonetwork.get_mut(protonode_index).unwrap() else {
// If a node with the same sni is reached, then it is deduplicated
let mut generated_snis = std::collections::HashSet::new();
// Editor metadata: map the original location to the stable node id for each inserted protonode
let mut original_locations = Vec::new();
for current_protonode_index in 0..protonetwork.len() {
let ProtonodeEntry::Protonode(protonode) = protonetwork.get_mut(current_protonode_index).unwrap() else {
panic!("No protonode can be deduplicated during flattening");
};
// Generate context dependencies. If None, then it is a value node and does not require nullification
let mut protonode_context_dependencies = None;
if let ConstructionArgs::Nodes(NodeConstructionArgs { inputs, context_dependencies, .. }) = &mut protonode.construction_args {
for upstream_metadata in inputs.iter() {
let Some(upstream_metadata) = upstream_metadata else {
panic!("All inputs should be when the upstream SNI was generated");
};
for upstream_dependency in upstream_metadata.context_dependencies.iter().flatten() {
if !context_dependencies.contains(upstream_dependency) {
context_dependencies.push(upstream_dependency.clone());
let (protonode_context_dependencies, upstream_is_value) = match &mut protonode.construction_args {
ConstructionArgs::Nodes(NodeConstructionArgs { inputs, context_dependencies, .. }) => {
for upstream_metadata in inputs.iter() {
let Some(upstream_metadata) = upstream_metadata else {
panic!("All inputs should be when the upstream SNI was generated");
};
if upstream_metadata.is_value {
context_dependencies.add_dependencies(&upstream_metadata.context_dependencies);
}
}
}
// The context_dependencies are now the union of all inputs and the dependencies of the protonode. Set the dependencies of each input to the difference, which represents the data to nullify
for upstream_metadata in inputs.iter_mut() {
let Some(upstream_metadata) = upstream_metadata else {
panic!("All inputs should be when the upstream SNI was generated");
};
match upstream_metadata.context_dependencies.as_ref() {
Some(upstream_dependencies) => {
upstream_metadata.context_dependencies = Some(
context_dependencies
.iter()
.filter(|protonode_dependency| !upstream_dependencies.contains(protonode_dependency))
.cloned()
.collect::<Vec<_>>(),
)
// The context_dependencies are now the union of all inputs and the dependencies of the protonode. Set the dependencies of each input to the difference, which represents the data to nullify
for upstream_metadata in inputs.iter_mut() {
let Some(upstream_metadata) = upstream_metadata else {
panic!("All inputs should be when the upstream SNI was generated");
};
match upstream_metadata.is_value {
true => upstream_metadata.context_dependencies.difference(&context_dependencies),
// If the upstream node is a Value node, do not nullify the context
false => upstream_metadata.context_dependencies = ContextDependencies::none(),
}
// If none then the upstream node is a Value node, so do not nullify the context
None => upstream_metadata.context_dependencies = Some(Vec::new()),
}
(context_dependencies.clone(), false)
}
protonode_context_dependencies = Some(context_dependencies.clone());
}
protonode.generate_stable_node_id();
let current_stable_node_id = protonode.stable_node_id;
// If the stable node id is the same as a previous node, then deduplicate
let callers = if let Some(upstream_index) = generated_snis_to_index.get(&protonode.stable_node_id) {
let ProtonodeEntry::Protonode(deduplicated_protonode) = std::mem::replace(&mut protonetwork[protonode_index], ProtonodeEntry::Deduplicated(*upstream_index)) else {
panic!("Reached protonode must not be deduplicated");
};
let ProtonodeEntry::Protonode(upstream_protonode) = &mut protonetwork[*upstream_index] else {
panic!("Upstream protonode must not be deduplicated");
};
match deduplicated_protonode.construction_args {
ConstructionArgs::Value(node_value_args) => {
let ConstructionArgs::Value(upstream_value_args) = &mut upstream_protonode.construction_args else {
panic!("Upstream protonode must match current protonode construction args");
};
upstream_value_args.connector_paths.extend(node_value_args.connector_paths);
}
ConstructionArgs::Nodes(node_construction_args) => {
let ConstructionArgs::Nodes(upstream_value_args) = &mut upstream_protonode.construction_args else {
panic!("Upstream protonode must match current protonode construction args");
};
upstream_value_args.node_paths.extend(node_construction_args.node_paths);
// The dependencies of the deduplicated node and the upstream node are the same because all inputs are the same
}
ConstructionArgs::Inline(_) => todo!(),
}
// Set the caller of the upstream node to be the minimum of all deduplicated nodes and itself
upstream_protonode.caller = deduplicated_protonode.callers.iter().chain(upstream_protonode.caller.iter()).min().cloned();
deduplicated_protonode.callers
} else {
generated_snis_to_index.insert(protonode.stable_node_id, protonode_index);
protonode.caller = protonode.callers.iter().min().cloned();
std::mem::take(&mut protonode.callers)
// If its a value node (or extract?) then do not nullify when calling since there is no cache node placed on the output
_ => (ContextDependencies::none(), true),
};
// This runs for all protonodes
for (caller_path, input_index) in callers {
let caller_index = protonode_indices[&caller_path];
protonode.generate_stable_node_id();
let stable_node_id = protonode.stable_node_id;
// If the stable node id is the same as a previous node, then deduplicate
let (callers, original_location) = if !generated_snis.insert(stable_node_id) {
let ProtonodeEntry::Protonode(deduplicated_protonode) = std::mem::replace(&mut protonetwork[current_protonode_index], ProtonodeEntry::Deduplicated) else {
panic!("Reached protonode cannot already be deduplicated");
};
(deduplicated_protonode.callers, deduplicated_protonode.original_location)
} else {
(
std::mem::take(&mut protonode.callers),
std::mem::replace(&mut protonode.original_location, OriginalLocation::Node(Vec::new())),
)
};
// Map the callers inputs to the generated stable node id
for (caller, input_index) in callers {
let caller_index = protonode_indices[&caller];
let ProtonodeEntry::Protonode(caller_protonode) = &mut protonetwork[caller_index] else {
panic!("Downstream caller cannot be deduplicated");
};
match &mut caller_protonode.construction_args {
ConstructionArgs::Nodes(nodes) => {
assert!(caller_index > protonode_index, "Caller index must be higher than current index");
let input_metadata: &mut Option<UpstreamInputMetadata> = &mut nodes.inputs[input_index];
if input_metadata.is_none() {
*input_metadata = Some(UpstreamInputMetadata {
input_sni: current_stable_node_id,
context_dependencies: protonode_context_dependencies.clone(),
})
}
assert!(caller_index > current_protonode_index, "Caller index must be higher than current index");
nodes.inputs[input_index] = Some(UpstreamInputMetadata {
input_sni: stable_node_id,
context_dependencies: protonode_context_dependencies.clone(),
is_value: upstream_is_value,
})
}
// Value node cannot be a caller
ConstructionArgs::Value(_) => unreachable!(),
ConstructionArgs::Inline(_) => todo!(),
}
}
// Map the original location to the stable node id
original_locations.push((original_location, stable_node_id));
}
// Do another traversal now that the metadata has been accumulated after deduplication
// This includes the caller of all absolute value connections which have a NodeInput::Value, as well as the caller for each protonode
let mut value_connector_callers = Vec::new();
let mut protonode_callers = Vec::new();
// Collect caller ids into a separate vec so that the pronetwork can be mutably iterated over to take the connector/node paths rather than cloning
let calling_protonode_ids = protonetwork
.iter()
.map(|entry| match entry {
ProtonodeEntry::Protonode(proto_node) => proto_node.stable_node_id,
ProtonodeEntry::Deduplicated(upstream_protonode_index) => {
let ProtonodeEntry::Protonode(proto_node) = &protonetwork[*upstream_protonode_index] else {
panic!("Upstream protonode index must not be dedeuplicated");
};
proto_node.stable_node_id
}
})
.collect::<Vec<_>>();
for protonode_entry in &mut protonetwork {
if let ProtonodeEntry::Protonode(protonode) = protonode_entry {
if let Some((caller_path, caller_input_index)) = protonode.caller.as_ref() {
let caller_index = protonode_indices[caller_path];
match &mut protonode.construction_args {
ConstructionArgs::Value(node_value_args) => {
value_connector_callers.push((std::mem::take(&mut node_value_args.connector_paths), (calling_protonode_ids[caller_index], *caller_input_index)))
}
ConstructionArgs::Nodes(node_construction_args) => {
protonode_callers.push((std::mem::take(&mut node_construction_args.node_paths), (calling_protonode_ids[caller_index], *caller_input_index)))
}
ConstructionArgs::Inline(_) => todo!(),
}
}
}
}
Ok((ProtoNetwork::from_vec(protonetwork), value_connector_callers, protonode_callers))
Ok((ProtoNetwork::from_vec(protonetwork), original_locations))
}
fn get_input_from_absolute_connector(&mut self, traversal_input: &AbsoluteInputConnector) -> Option<&mut NodeInput> {
@@ -728,7 +662,7 @@ impl NodeNetwork {
protonetwork: &mut Vec<ProtonodeEntry>, // None represents a deduplicated value node
// Every time a value input is reached, it is added to a mapping so if it reached again, it can be moved to the end of the protonetwork
value_protonode_indices: &mut HashMap<AbsoluteInputConnector, usize>,
// Every time a protonode is reached, is it added to a mapping so if it reached again, it can be moved to the end of the protonetwork
// Every time a protonode is reached, is it added to a mapping so if it reached again
protonode_indices: &mut HashMap<ProtonodePath, usize>,
// The original location of the current traversal
traversal_input: AbsoluteInputConnector,
@@ -827,12 +761,11 @@ impl NodeNetwork {
Some((upstream_node_path.clone(), input_index)),
);
}
let context_dependencies = NODE_CONTEXT_DEPENDENCY.lock().unwrap().get(identifier.name.as_ref()).cloned().unwrap_or_default();
let context_dependencies = NODE_CONTEXT_DEPENDENCY.lock().unwrap().get(identifier.name.as_ref()).cloned().unwrap_or(ContextDependencies::none());
let construction_args = ConstructionArgs::Nodes(NodeConstructionArgs {
identifier,
inputs: vec![None; number_of_inputs],
context_dependencies,
node_paths: Vec::new(),
});
let protonode = ProtoNode {
construction_args,
@@ -840,11 +773,11 @@ impl NodeNetwork {
input: concrete!(Context),
stable_node_id: NodeId(0),
callers: Vec::new(),
caller: None,
original_location: OriginalLocation::Node(upstream_node_path.clone()),
};
let new_protonode_index = protonetwork.len();
protonetwork.push(ProtonodeEntry::Protonode(protonode));
protonode_indices.insert(upstream_node_path.clone(), new_protonode_index);
protonode_indices.insert(upstream_node_path, new_protonode_index);
let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else {
panic!("Inserted protonode must exist at new_protonode_index");
};
@@ -855,10 +788,6 @@ impl NodeNetwork {
if let Some(traversal_start) = traversal_start {
reached_protonode.callers.push(traversal_start);
}
let ConstructionArgs::Nodes(args) = &mut reached_protonode.construction_args else {
panic!("Reached protonode must have Nodes construction args");
};
args.node_paths.push(upstream_node_path);
}
DocumentNodeImplementation::Extract => todo!(),
}
@@ -876,18 +805,15 @@ impl NodeNetwork {
// Insert the protonode and traverse over inputs
None => {
let value_protonode = ProtoNode {
construction_args: ConstructionArgs::Value(NodeValueArgs {
value: std::mem::replace(tagged_value, TaggedValue::None.into()),
connector_paths: Vec::new(),
}),
construction_args: ConstructionArgs::Value(std::mem::replace(tagged_value, TaggedValue::None.into())),
input: concrete!(Context), // Could be ()
stable_node_id: NodeId(0),
callers: Vec::new(),
caller: None,
original_location: OriginalLocation::Value(traversal_input.clone()),
};
let new_protonode_index = protonetwork.len();
protonetwork.push(ProtonodeEntry::Protonode(value_protonode));
value_protonode_indices.insert(traversal_input.clone(), new_protonode_index);
value_protonode_indices.insert(traversal_input, new_protonode_index);
let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else {
panic!("Previously inserted protonode must exist at mapped protonode index");
@@ -895,15 +821,10 @@ impl NodeNetwork {
protonode
}
};
// Only add the traversal start if it is not the root export
if let Some(traversal_start) = traversal_start {
reached_protonode.callers.push(traversal_start);
}
let ConstructionArgs::Value(args) = &mut reached_protonode.construction_args else {
panic!("Reached protonode must have Nodes construction args");
};
args.connector_paths.push(traversal_input);
}
// Continue traversal
NodeInput::Network { import_index, .. } => {
@@ -969,17 +890,15 @@ impl NodeNetwork {
#[derive(Debug, Clone)]
pub enum ProtonodeEntry {
Protonode(ProtoNode),
// If deduplicated, then any upstream node which this node previously called needs to map to the new protonode
Deduplicated(usize),
// A node is deduplicated if it has the same stable node id,
Deduplicated,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CompilationMetadata {
// Stored for every value input in the compiled network
pub protonode_caller_for_values: Vec<(Vec<AbsoluteInputConnector>, CompiledProtonodeInput)>,
// Stored for every protonode in the compiled network
pub protonode_caller_for_nodes: Vec<(Vec<ProtonodePath>, CompiledProtonodeInput)>,
pub types_to_add: Vec<(SNI, Vec<Type>)>,
pub types_to_remove: Vec<(SNI, usize)>,
pub original_locations: Vec<(OriginalLocation, SNI)>,
pub types_to_add: Vec<(SNI, NodeIOTypes)>,
pub types_to_remove: Vec<SNI>,
}
//An Input connector with a node path for unique identification
@@ -1112,7 +1031,7 @@ impl<'a> Iterator for RecursiveNodeIter<'a> {
}
}
#[cfg(test)]
// #[cfg(test)]
// mod test {
// use super::*;
// use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
@@ -1372,65 +1291,65 @@ impl<'a> Iterator for RecursiveNodeIter<'a> {
// }
// }
// fn two_node_identity() -> NodeNetwork {
// NodeNetwork {
// exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)],
// nodes: [
// (
// NodeId(1),
// DocumentNode {
// inputs: vec![NodeInput::network(concrete!(u32), 0)],
// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
// ..Default::default()
// },
// ),
// (
// NodeId(2),
// DocumentNode {
// inputs: vec![NodeInput::network(concrete!(u32), 1)],
// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
// ..Default::default()
// },
// ),
// ]
// .into_iter()
// .collect(),
// ..Default::default()
// }
// }
// fn two_node_identity() -> NodeNetwork {
// NodeNetwork {
// exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)],
// nodes: [
// (
// NodeId(1),
// DocumentNode {
// inputs: vec![NodeInput::network(concrete!(u32), 0)],
// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
// ..Default::default()
// },
// ),
// (
// NodeId(2),
// DocumentNode {
// inputs: vec![NodeInput::network(concrete!(u32), 1)],
// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
// ..Default::default()
// },
// ),
// ]
// .into_iter()
// .collect(),
// ..Default::default()
// }
// }
// fn output_duplicate(network_outputs: Vec<NodeInput>, result_node_input: NodeInput) -> NodeNetwork {
// let mut network = NodeNetwork {
// exports: network_outputs,
// nodes: [
// (
// NodeId(1),
// DocumentNode {
// inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)],
// implementation: DocumentNodeImplementation::Network(two_node_identity()),
// ..Default::default()
// },
// ),
// (
// NodeId(2),
// DocumentNode {
// inputs: vec![result_node_input],
// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
// ..Default::default()
// },
// ),
// ]
// .into_iter()
// .collect(),
// ..Default::default()
// };
// let _new_ids = 101..;
// network.populate_dependants();
// network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10000));
// network.flatten_with_fns(NodeId(2), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10001));
// network.remove_dead_nodes(0);
// network
// }
// fn output_duplicate(network_outputs: Vec<NodeInput>, result_node_input: NodeInput) -> NodeNetwork {
// let mut network = NodeNetwork {
// exports: network_outputs,
// nodes: [
// (
// NodeId(1),
// DocumentNode {
// inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)],
// implementation: DocumentNodeImplementation::Network(two_node_identity()),
// ..Default::default()
// },
// ),
// (
// NodeId(2),
// DocumentNode {
// inputs: vec![result_node_input],
// implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
// ..Default::default()
// },
// ),
// ]
// .into_iter()
// .collect(),
// ..Default::default()
// };
// let _new_ids = 101..;
// network.populate_dependants();
// network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10000));
// network.flatten_with_fns(NodeId(2), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), || NodeId(10001));
// network.remove_dead_nodes(0);
// network
// }
// #[test]
// fn simple_duplicate() {

View File

@@ -1,4 +1,5 @@
use crate::document::{AbsoluteInputConnector, InlineRust, ProtonodeEntry, value};
use crate::document::value::TaggedValue;
use crate::document::{AbsoluteInputConnector, InlineRust, ProtonodeEntry};
pub use graphene_core::registry::*;
use graphene_core::uuid::{NodeId, ProtonodePath, SNI};
use graphene_core::*;
@@ -22,11 +23,8 @@ impl ProtoNetwork {
let last_entry = nodes.last().expect("Cannot compile empty protonetwork");
let output = match last_entry {
ProtonodeEntry::Protonode(proto_node) => proto_node.stable_node_id,
ProtonodeEntry::Deduplicated(deduplicated_index) => {
let ProtonodeEntry::Protonode(protonode) = &nodes[*deduplicated_index] else {
panic!("Deduplicated protonode must point to valid protonode");
};
protonode.stable_node_id
ProtonodeEntry::Deduplicated => {
panic!("Not possible for the output protonode to be deduplicated");
}
};
ProtoNetwork { nodes, output }
@@ -97,9 +95,10 @@ impl ProtoNetwork {
#[derive(Clone, Debug)]
pub struct UpstreamInputMetadata {
pub input_sni: SNI,
// Context dependencies are accumulated during compilation, then replaced with whatever needs to be nullified
// If None, then the upstream node is a value node, so replace with an empty vec
pub context_dependencies: Option<Vec<ContextDependency>>,
// Context dependencies are accumulated during compilation, then replaced with the difference between the node's dependencies and the inputs dependencies
pub context_dependencies: ContextDependencies,
// If the upstream node is a value node, then do not nullify since the value nodes do not have a cache inserted after them
pub is_value: bool,
}
#[derive(Debug, Clone)]
@@ -112,24 +111,14 @@ pub struct NodeConstructionArgs {
// Starts as None, and is populated during stable node id generation
pub inputs: Vec<Option<UpstreamInputMetadata>>,
// The union of all input context dependencies and the nodes context dependency. Used to generate the context nullification for the editor entry point
pub context_dependencies: Vec<ContextDependency>,
// Stores the path of document nodes which correspond to it
pub node_paths: Vec<ProtonodePath>,
}
#[derive(Debug, Clone)]
pub struct NodeValueArgs {
/// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe)
/// Also stores its caller inputs, which is used to map the rendered thumbnail to the wire input
pub value: MemoHash<value::TaggedValue>,
// Stores all absolute input connectors which correspond to this value.
pub connector_paths: Vec<AbsoluteInputConnector>,
pub context_dependencies: ContextDependencies,
}
#[derive(Debug, Clone)]
/// Defines the arguments used to construct the boxed node struct. This is used to call the constructor function in the `node_registry.rs` file - which is hidden behind a wall of macros.
pub enum ConstructionArgs {
Value(NodeValueArgs),
/// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe)
Value(MemoHash<TaggedValue>),
Nodes(NodeConstructionArgs),
/// Used for GPU computation to work around the limitations of rust-gpu.
Inline(InlineRust),
@@ -152,25 +141,28 @@ pub enum ConstructionArgs {
// If the the protonode has ConstructionArgs::Value, then its identifier is not used, and is replaced with an UpcastNode with a value of the tagged value
pub struct ProtoNode {
pub construction_args: ConstructionArgs,
pub original_location: OriginalLocation,
pub input: Type,
pub stable_node_id: SNI,
// Each protonode stores the path and input index of the protonodes which called it
// Each protonode stores the input of the protonode which called it in order to map input SNI
pub callers: Vec<(ProtonodePath, usize)>,
// Each protonode will finally store a single caller (the minimum of all callers), used by the editor
pub caller: Option<(ProtonodePath, usize)>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
/// Stores the origin of the protonode in the document network``, which is either an inserted value protonode SNI for an input connector, or a protonode SNI for a protonode
pub enum OriginalLocation {
Value(AbsoluteInputConnector),
Node(ProtonodePath),
}
impl Default for ProtoNode {
fn default() -> Self {
Self {
construction_args: ConstructionArgs::Value(NodeValueArgs {
value: value::TaggedValue::U32(0).into(),
connector_paths: Vec::new(),
}),
construction_args: ConstructionArgs::Value(TaggedValue::U32(0).into()),
input: concrete!(Context),
stable_node_id: NodeId(0),
callers: Vec::new(),
caller: None,
original_location: OriginalLocation::Node(Vec::new()),
}
}
}
@@ -183,7 +175,10 @@ impl ProtoNode {
input: concrete!(Context),
stable_node_id,
callers: Vec::new(),
caller: None,
original_location: OriginalLocation::Value(AbsoluteInputConnector {
network_path: Vec::new(),
connector: crate::document::InputConnector::Export(0),
}),
}
}
@@ -198,7 +193,7 @@ impl ProtoNode {
}
nodes.identifier.hash(&mut hasher);
}
ConstructionArgs::Value(value) => value.value.hash(&mut hasher),
ConstructionArgs::Value(value) => value.hash(&mut hasher),
ConstructionArgs::Inline(_) => todo!(),
}
@@ -214,6 +209,7 @@ pub enum GraphErrorType {
NoConstructor,
InvalidImplementations { inputs: String, error_inputs: Vec<Vec<(usize, (Type, Type))>> },
MultipleImplementations { inputs: String, valid: Vec<NodeIOTypes> },
UnresolvedType,
}
impl Debug for GraphErrorType {
// TODO: format with the document graph context so the input index is the same as in the graph UI.
@@ -257,25 +253,27 @@ impl Debug for GraphErrorType {
)
}
GraphErrorType::MultipleImplementations { inputs, valid } => write!(f, "Multiple implementations found ({inputs}):\n{valid:#?}"),
GraphErrorType::UnresolvedType => write!(f, "Could not determine type of node"),
}
}
}
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct GraphError {
pub stable_node_id: SNI,
pub original_location: OriginalLocation,
pub identifier: Cow<'static, str>,
pub error: GraphErrorType,
}
impl GraphError {
pub fn new(node: &ProtoNode, text: impl Into<GraphErrorType>) -> Self {
let identifier = match &node.construction_args {
pub fn new(construction_args: &ConstructionArgs, original_location: OriginalLocation, text: impl Into<GraphErrorType>) -> Self {
let identifier = match &construction_args {
ConstructionArgs::Nodes(node_construction_args) => node_construction_args.identifier.name.clone(),
// Values are inserted into upcast nodes
ConstructionArgs::Value(node_value_args) => format!("{:?} Value Node", node_value_args.value.deref().ty()).into(),
ConstructionArgs::Value(value) => format!("{:?} Value Node", value.deref().ty()).into(),
ConstructionArgs::Inline(_) => "Inline".into(),
};
Self {
stable_node_id: node.stable_node_id,
original_location,
identifier,
error: text.into(),
}
@@ -339,10 +337,10 @@ impl TypingContext {
}
/// Returns the inferred types for a given node id.
pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<NodeIOTypes, GraphErrors> {
pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<(), GraphErrors> {
// Return the inferred type if it is already known
if let Some(inferred) = self.inferred.get(&node_id) {
return Ok(inferred.clone());
if self.inferred.contains_key(&node_id) {
return Ok(());
}
let (inputs, id) = match node.construction_args {
@@ -350,9 +348,9 @@ impl TypingContext {
ConstructionArgs::Value(ref v) => {
// assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context)));
// TODO: This should return a reference to the value
let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.value.ty())), vec![]);
self.inferred.insert(node_id, types.clone());
return Ok(types);
let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]);
self.inferred.insert(node_id, types);
return Ok(());
}
// If the node has nodes as inputs we can infer the types from the node outputs
ConstructionArgs::Nodes(ref construction_args) => {
@@ -363,7 +361,7 @@ impl TypingContext {
.map(|id| {
self.inferred
.get(&id)
.ok_or_else(|| vec![GraphError::new(node, GraphErrorType::InputNodeNotFound(id))])
.ok_or_else(|| vec![GraphError::new(&node.construction_args, node.original_location.clone(), GraphErrorType::InputNodeNotFound(id))])
.map(|node| node.ty())
})
.collect::<Result<Vec<Type>, GraphErrors>>()?;
@@ -372,18 +370,24 @@ impl TypingContext {
ConstructionArgs::Inline(ref inline) => (vec![inline.ty.clone()], &*Box::new(ProtoNodeIdentifier::new("Extract"))),
};
let impls = self.lookup.get(id).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?;
let Some(impls) = self.lookup.get(id) else {
return Err(vec![GraphError::new(&node.construction_args, node.original_location.clone(), GraphErrorType::NoImplementations)]);
};
if let Some(index) = inputs.iter().position(|p| {
matches!(p,
Type::Fn(_, b) if matches!(b.as_ref(), Type::Generic(_)))
}) {
return Err(vec![GraphError::new(node, GraphErrorType::UnexpectedGenerics { index, inputs })]);
return Err(vec![GraphError::new(
&node.construction_args,
node.original_location.clone(),
GraphErrorType::UnexpectedGenerics { index, inputs },
)]);
}
/// Checks if a proposed input to a particular (primary or secondary) input connector is valid for its type signature.
/// `from` indicates the value given to a input, `to` indicates the input's allowed type as specified by its type signature.
fn valid_type(from: &Type, to: &Type) -> bool {
pub fn valid_type(from: &Type, to: &Type) -> bool {
match (from, to) {
// Direct comparison of two concrete types.
(Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2,
@@ -464,15 +468,17 @@ impl TypingContext {
.map(|(i, t)| {let input_number = i + 1; format!("• Input {input_number}: {t}")})
.collect::<Vec<_>>()
.join("\n");
Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })])
Err(vec![GraphError::new(
&node.construction_args,
node.original_location.clone(),
GraphErrorType::InvalidImplementations { inputs, error_inputs },
)])
}
[(node_io, org_nio)] => {
let node_io = node_io.clone();
// Save the inferred type
self.inferred.insert(node_id, node_io.clone());
self.constructor.insert(node_id, impls[org_nio]);
Ok(node_io)
Ok(())
}
// If two types are available and one of them accepts () an input, always choose that one
[first, second] => {
@@ -485,18 +491,25 @@ impl TypingContext {
// Save the inferred type
self.inferred.insert(node_id, node_io.clone());
self.constructor.insert(node_id, impls[orig_nio]);
return Ok(node_io.clone());
return Ok(());
}
}
let inputs = [&node.input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let valid = valid_output_types.into_iter().cloned().collect();
Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })])
Err(vec![GraphError::new(
&node.construction_args,
node.original_location.clone(),
GraphErrorType::MultipleImplementations { inputs, valid },
)])
}
_ => {
let inputs = [&node.input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let valid = valid_output_types.into_iter().cloned().collect();
Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })])
Err(vec![GraphError::new(
&node.construction_args,
node.original_location.clone(),
GraphErrorType::MultipleImplementations { inputs, valid },
)])
}
}
}

View File

@@ -3,7 +3,7 @@ use glam::DAffine2;
pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer};
use graphene_core::Context;
use graphene_core::ContextDependency;
use graphene_core::ContextDependencies;
use graphene_core::NodeIO;
use graphene_core::OwnedContextImpl;
use graphene_core::WasmNotSend;
@@ -52,25 +52,25 @@ pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> Do
DowncastBothNode::new(n)
}
pub struct EditorContextToContext {
first: SharedNodeContainer,
}
// pub struct EditorContextToContext {
// first: SharedNodeContainer,
// }
impl<'i> Node<'i, Any<'i>> for EditorContextToContext {
type Output = DynFuture<'i, Any<'i>>;
fn eval(&'i self, input: Any<'i>) -> Self::Output {
Box::pin(async move {
let editor_context = dyn_any::downcast::<EditorContext>(input).unwrap();
self.first.eval(Box::new(editor_context.to_context())).await
})
}
}
// impl<'i> Node<'i, Any<'i>> for EditorContextToContext {
// type Output = DynFuture<'i, Any<'i>>;
// fn eval(&'i self, input: Any<'i>) -> Self::Output {
// Box::pin(async move {
// let editor_context = dyn_any::downcast::<EditorContext>(input).unwrap();
// self.first.eval(Box::new(editor_context.to_context())).await
// })
// }
// }
impl EditorContextToContext {
pub const fn new(first: SharedNodeContainer) -> Self {
EditorContextToContext { first }
}
}
// impl EditorContextToContext {
// pub const fn new(first: SharedNodeContainer) -> Self {
// EditorContextToContext { first }
// }
// }
#[derive(Debug, Clone, Default)]
pub struct EditorContext {
@@ -101,7 +101,7 @@ unsafe impl StaticType for EditorContext {
// }
impl EditorContext {
pub fn to_context(&self) -> Context {
pub fn to_owned_context(&self) -> OwnedContextImpl {
let mut context = OwnedContextImpl::default();
if let Some(footprint) = self.footprint {
context.set_footprint(footprint);
@@ -121,42 +121,44 @@ impl EditorContext {
if let Some(index) = self.index {
context.set_index(index);
}
context
// if let Some(editor_var_args) = self.editor_var_args {
// let (variable_names, values)
// context.set_varargs((variable_names, values))
// }
context.into_context()
}
}
pub struct NullificationNode {
first: SharedNodeContainer,
nullify: Vec<ContextDependency>,
nullify: ContextDependencies,
}
impl<'i> Node<'i, Any<'i>> for NullificationNode {
type Output = DynFuture<'i, Any<'i>>;
fn eval(&'i self, input: Any<'i>) -> Self::Output {
let new_input = match dyn_any::try_downcast::<Context>(input) {
Ok(context) => match *context {
Some(context) => {
let mut new_context = OwnedContextImpl::from(context);
new_context.nullify(&self.nullify);
Box::new(new_context.into_context()) as Any<'i>
}
None => {
let none: Context = None;
Box::new(none) as Any<'i>
}
},
Err(other_input) => other_input,
};
Box::pin(async move { self.first.eval(new_input).await })
Box::pin(async move {
let new_input = match dyn_any::try_downcast::<Context>(input) {
Ok(context) => match *context {
Some(context) => {
let mut new_context: OwnedContextImpl = OwnedContextImpl::from(context);
new_context.nullify(&self.nullify);
Box::new(new_context.into_context()) as Any<'i>
}
None => {
let none: Context = None;
Box::new(none) as Any<'i>
}
},
Err(other_input) => other_input,
};
self.first.eval(new_input).await
})
}
}
impl NullificationNode {
pub fn new(first: SharedNodeContainer, nullify: Vec<ContextDependency>) -> Self {
pub fn new(first: SharedNodeContainer, nullify: ContextDependencies) -> Self {
Self { first, nullify }
}
}

View File

@@ -1,13 +1,12 @@
use crate::node_registry::{CACHE_NODES, NODE_REGISTRY};
use dyn_any::StaticType;
use dyn_any::{Any, StaticType};
use graph_craft::document::value::{TaggedValue, UpcastNode};
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, UpstreamInputMetadata};
use graph_craft::proto::{GraphErrorType, GraphErrors};
use graph_craft::{Type, concrete};
use graphene_std::Context;
use graphene_std::any::{EditorContext, EditorContextToContext, NullificationNode};
use graphene_std::memo::IntrospectMode;
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI};
use graphene_std::any::{EditorContext, NullificationNode};
use graphene_std::uuid::{NodeId, SNI};
use graphene_std::{Context, ContextDependencies, NodeIOTypes};
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::sync::Arc;
@@ -46,18 +45,13 @@ impl DynamicExecutor {
/// 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<(Vec<(SNI, Vec<Type>)>, Vec<(SNI, usize)>), GraphErrors> {
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(Vec<(SNI, NodeIOTypes)>, Vec<SNI>), GraphErrors> {
self.output = Some(proto_network.output);
self.typing_context.update(&proto_network)?;
// 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 {
if let Some(number_of_inputs) = self.tree.free_node(&sni) {
remove.push((sni, number_of_inputs));
}
remove.push(sni);
self.typing_context.remove_inference(&sni);
}
@@ -65,37 +59,20 @@ impl DynamicExecutor {
.into_iter()
.filter_map(|sni| {
let Some(types) = self.typing_context.type_of(sni) else {
log::error!("Could not get type for sni: {:?}", sni);
return None;
};
Some((sni, types.inputs.clone()))
Some((sni, types.clone()))
})
.collect();
Ok((add_with_types, remove))
}
/// 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<Option<Arc<dyn std::any::Any + Send + Sync>>, IntrospectError> {
let node = self.get_introspect_node_container(protonode_input)?;
Ok(node.introspect(introspect_mode))
}
pub fn set_introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) {
let Ok(node) = self.get_introspect_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_introspect_node_container(&self, protonode_input: CompiledProtonodeInput) -> Result<SharedNodeContainer, IntrospectError> {
// The SNI of the monitor nodes are the ids of the protonode + input index
let inserted_node = self.tree.nodes.get(&protonode_input.0).ok_or(IntrospectError::ProtoNodeNotFound(protonode_input))?;
let node = inserted_node
.input_introspection_entrypoints
.get(protonode_input.1)
.ok_or(IntrospectError::InputIndexOutOfBounds(protonode_input))?;
Ok(node.clone())
// Introspect the cached output of any protonode
pub fn introspect(&self, protonode: SNI, check_if_evaluated: bool) -> Result<Option<Arc<dyn std::any::Any + Send + Sync>>, IntrospectError> {
let inserted_node = self.tree.nodes.get(&protonode).ok_or(IntrospectError::ProtoNodeNotFound(protonode))?;
Ok(inserted_node.cached_protonode.introspect(check_if_evaluated))
}
pub fn input_type(&self) -> Option<Type> {
@@ -124,6 +101,7 @@ impl DynamicExecutor {
.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())?;
// A node to convert the EditorContext to the Context is automatically inserted for each node at id-1
let result = match input_type {
t if t == concrete!(Context) => self.execute(editor_context, node_to_evaluate).await.map_err(|e| e.to_string()),
@@ -162,9 +140,8 @@ impl DynamicExecutor {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IntrospectError {
PathNotFound(Vec<NodeId>),
ProtoNodeNotFound(CompiledProtonodeInput),
InputIndexOutOfBounds(CompiledProtonodeInput),
InvalidInputType(CompiledProtonodeInput),
ProtoNodeNotFound(SNI),
// InvalidInputType(SNI),
NoData,
RuntimeNotReady,
IntrospectNotImplemented,
@@ -174,31 +151,21 @@ impl std::fmt::Display for IntrospectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IntrospectError::PathNotFound(path) => write!(f, "Path not found: {:?}", path),
IntrospectError::ProtoNodeNotFound(input) => write!(f, "ProtoNode not found: {:?}", input),
IntrospectError::ProtoNodeNotFound(node) => write!(f, "ProtoNode not found during: {:?}", node),
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"),
IntrospectError::InputIndexOutOfBounds(input) => write!(f, "Invalid input index: {:?}", input),
IntrospectError::InvalidInputType(input) => write!(f, "Invalid input type: {:?}", input),
// IntrospectError::InvalidInputType(input) => write!(f, "Invalid input type: {:?}", input),
}
}
}
#[derive(Clone)]
struct InsertedProtonode {
// If the inserted protonode is a value node, then do not clear types when removing
is_value: bool,
// Either the value node, cache node, or protonode if output is not clone
// Either the value node, cache node if output is clone, or protonode if output is not clone
cached_protonode: SharedNodeContainer,
// Value nodes are the entry points, since they can be directly evaluated
// Nodes with cloneable outputs have a cache, then editor entry point
// Nodes without cloneable outputs just have an editor entry point connected to their output
output_editor_entrypoint: SharedNodeContainer,
// Nodes with inputs store references to the entry points of the upstream node
// This is used to generate thumbnails
input_thumbnail_entrypoints: Vec<SharedNodeContainer>,
// They also store references to the upstream cache/value node, used for introspection
input_introspection_entrypoints: Vec<SharedNodeContainer>,
// A list of arguments in the context to nullify when executing the node
nullify_when_calling: ContextDependencies,
}
/// A store of dynamically typed nodes and their associated source map.
@@ -243,10 +210,7 @@ impl BorrowTree {
let sni = node.stable_node_id;
old_nodes.remove(&sni);
if !self.nodes.contains_key(&sni) {
// Do not send types for auto inserted value nodes
if matches!(node.construction_args, ConstructionArgs::Nodes(_)) {
nodes_with_new_type.push(sni)
}
nodes_with_new_type.push(sni);
self.push_node(node, typing_context).await?;
}
}
@@ -262,23 +226,35 @@ impl BorrowTree {
}
/// Evaluate any node in the borrow tree
pub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option<O>
where
I: StaticType + 'i + Send + Sync,
O: StaticType + 'i,
{
let node = self.nodes.get(&id)?;
let output = node.output_editor_entrypoint.eval(Box::new(input));
dyn_any::downcast::<O>(output.await).ok().map(|o| *o)
}
// pub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option<O>
// where
// I: StaticType + 'i + Send + Sync,
// O: StaticType + 'i,
// {
// let node = self.nodes.get(&id)?;
// let output = node.output_editor_entrypoint.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: SNI, input: I) -> Result<TaggedValue, String>
pub async fn eval_tagged_value<'i, I>(&'i self, id: SNI, input: I) -> Result<TaggedValue, String>
where
I: StaticType + 'static + Send + Sync,
{
let inserted_node = self.nodes.get(&id).ok_or("Output node not found in executor")?;
let output = inserted_node.output_editor_entrypoint.eval(Box::new(input));
// Try convert the editor context to a nullified Context, since the Context is not StaticType
let new_input = match dyn_any::try_downcast::<EditorContext>(Box::new(input)) {
Ok(editor_context) => {
let mut context = editor_context.to_owned_context();
context.nullify(&inserted_node.nullify_when_calling);
Box::new(context.into_context()) as Any<'i>
}
Err(other_input) => other_input,
};
let output = inserted_node.cached_protonode.eval(new_input);
TaggedValue::try_from_any(output.await)
}
@@ -337,9 +313,8 @@ 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: &SNI) -> Option<usize> {
let removed_node = self.nodes.remove(&id).expect(&format!("Could not remove node: {:?}", id));
removed_node.is_value.then_some(removed_node.input_thumbnail_entrypoints.len())
pub fn free_node(&mut self, id: &SNI) {
self.nodes.remove(&id).expect("Node could not be removed");
}
/// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`.
@@ -360,31 +335,37 @@ impl BorrowTree {
/// Thumbnails is a mapping of the protonode input to the rendered thumbnail through the monitor cache node
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_args) => {
let upcasted = UpcastNode::new(value_args.value);
ConstructionArgs::Value(value) => {
let upcasted = UpcastNode::new(value);
let node = Box::new(upcasted) as TypeErasedBox<'_>;
let value_node = NodeContainer::new(node);
let cached_protonode = NodeContainer::new(node);
let inserted_protonode = InsertedProtonode {
is_value: true,
cached_protonode: value_node.clone(),
output_editor_entrypoint: value_node,
input_thumbnail_entrypoints: Vec::new(),
input_introspection_entrypoints: Vec::new(),
cached_protonode,
nullify_when_calling: ContextDependencies::none(),
};
self.nodes.insert(sni, inserted_protonode);
}
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
ConstructionArgs::Nodes(node_construction_args) => {
let construction_nodes = self.node_deps(&node_construction_args.inputs);
let Some(types) = typing_context.type_of(sni) else {
return Err(vec![GraphError::new(
&ConstructionArgs::Nodes(node_construction_args),
proto_node.original_location,
GraphErrorType::UnresolvedType,
)]);
};
let input_thumbnail_entrypoints = construction_nodes
.iter()
.map(|inserted_protonode| inserted_protonode.output_editor_entrypoint.clone())
.collect::<Vec<_>>();
let input_introspection_entrypoints = construction_nodes.iter().map(|inserted_protonode| inserted_protonode.cached_protonode.clone()).collect::<Vec<_>>();
let Some(constructor) = typing_context.constructor(sni) else {
return Err(vec![GraphError::new(
&ConstructionArgs::Nodes(node_construction_args),
proto_node.original_location,
GraphErrorType::NoConstructor,
)]);
};
let construction_nodes = self.node_deps(&node_construction_args.inputs);
// Insert nullification if necessary
let protonode_inputs = construction_nodes
@@ -392,36 +373,20 @@ impl BorrowTree {
.zip(node_construction_args.inputs.into_iter())
.map(|(inserted_protonode, input_metadata)| {
let previous_input = inserted_protonode.cached_protonode.clone();
let input_context_dependencies = input_metadata.unwrap().context_dependencies.unwrap();
let protonode_input = if !input_context_dependencies.is_empty() {
let input_context_dependencies = input_metadata.unwrap().context_dependencies;
if !input_context_dependencies.is_empty() {
let nullification_node = NullificationNode::new(previous_input, input_context_dependencies);
let node = Box::new(nullification_node) as TypeErasedBox<'_>;
NodeContainer::new(node)
} else {
previous_input
};
protonode_input
}
})
.collect::<Vec<_>>();
let constructor = typing_context.constructor(sni).ok_or_else(|| {
vec![GraphError {
stable_node_id: sni,
identifier: node_construction_args.identifier.name.clone(),
error: GraphErrorType::NoConstructor,
}]
})?;
let node = constructor(protonode_inputs).await;
let protonode = NodeContainer::new(node);
let types = typing_context.type_of(sni).ok_or_else(|| {
vec![GraphError {
stable_node_id: sni,
identifier: node_construction_args.identifier.name,
error: GraphErrorType::NoConstructor,
}]
})?;
// Insert cache nodes on the output if possible
let cached_protonode = if let Some(cache_constructor) = typing_context.cache_constructor(&types.return_value.nested_type()) {
let cache = cache_constructor(protonode);
@@ -431,36 +396,17 @@ impl BorrowTree {
protonode
};
// If the call argument is Context, insert a conversion node between EditorContext to Context so that it can be evaluated
// Also insert the nullification node to whatever the protonode is not dependent on
let mut editor_entrypoint_input = cached_protonode.clone();
if types.call_argument == concrete!(Context) {
let nullify = graphene_std::all_context_dependencies()
.into_iter()
.filter(|dependency| !node_construction_args.context_dependencies.contains(dependency))
.collect::<Vec<_>>();
if !nullify.is_empty() {
let nullification_node = NullificationNode::new(cached_protonode.clone(), nullify);
let node = Box::new(nullification_node) as TypeErasedBox<'_>;
editor_entrypoint_input = NodeContainer::new(node)
}
}
let editor_entry_point = EditorContextToContext::new(editor_entrypoint_input);
let node = Box::new(editor_entry_point) as TypeErasedBox;
let output_editor_entrypoint = NodeContainer::new(node);
// When evaluating the node from the editor, nullify all context fields it is not dependent on
let nullify_when_calling = node_construction_args.context_dependencies.inverse();
let inserted_protonode = InsertedProtonode {
is_value: false,
cached_protonode,
output_editor_entrypoint,
input_thumbnail_entrypoints,
input_introspection_entrypoints,
nullify_when_calling,
};
self.nodes.insert(sni, inserted_protonode);
}
};
}
Ok(())
}
}
@@ -476,7 +422,7 @@ mod test {
let mut tree = BorrowTree::default();
let val_1_protonode = ProtoNode::value(
ConstructionArgs::Value(NodeValueArgs {
value: TaggedValue::U32(2u32).into(),
value: Some(TaggedValue::U32(2u32).into()),
connector_paths: Vec::new(),
}),
NodeId(0),
@@ -485,7 +431,7 @@ mod test {
let future = tree.push_node(val_1_protonode, &context);
futures::executor::block_on(future).unwrap();
let _node = tree.nodes.get(&NodeId(0)).expect("Node should be added to tree");
let result = futures::executor::block_on(tree.eval(NodeId(0), ()));
assert_eq!(result, Some(2u32));
let result = futures::executor::block_on(tree.eval_tagged_value(NodeId(0), ()));
assert_eq!(result, Some(TaggedValue::U32(2u32).into()));
}
}

View File

@@ -375,7 +375,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
mod #mod_name {
use super::*;
use #graphene_core as gcore;
use gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO, ContextDependency};
use gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO, ContextDependency, ContextDependencies};
use gcore::value::ClonedNode;
use gcore::ops::TypeNode;
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, NODE_CONTEXT_DEPENDENCY, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
@@ -436,10 +436,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
fn register_context_dependency() {
let mut context_dependency = NODE_CONTEXT_DEPENDENCY.lock().unwrap();
context_dependency.insert(
#identifier,
vec![
#identifier().to_string(),
ContextDependencies::from(vec![
#(ContextDependency::#context_dependencies,)*
]
])
);
}
}