mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Wire source ids through flattening, the nullification pass, and slot keying
This commit is contained in:
@@ -2406,7 +2406,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
&& let Some(reference) = document.network_interface.reference(node_id, network_path).clone()
|
||||
&& let Some(node_definition) = resolve_document_node_type(&reference)
|
||||
{
|
||||
let context_features = node_definition.node_template.document_node.context_features;
|
||||
let context_features = node_definition.node_template.document_node.context_features.clone();
|
||||
document.network_interface.set_context_features(node_id, network_path, context_features);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ pub use core_types::uuid::generate_uuid;
|
||||
use core_types::{Context, ContextDependencies, Cow, MemoHash, ProtoNodeIdentifier, Type};
|
||||
use dyn_any::DynAny;
|
||||
use glam::IVec2;
|
||||
use log::Metadata;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
@@ -215,12 +214,14 @@ impl InlineRust {
|
||||
#[derive(Debug, Clone, PartialEq, Hash, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DocumentNodeMetadata {
|
||||
DocumentNodePath,
|
||||
SourceId,
|
||||
}
|
||||
|
||||
impl DocumentNodeMetadata {
|
||||
pub fn ty(&self) -> Type {
|
||||
match self {
|
||||
DocumentNodeMetadata::DocumentNodePath => concrete!(core_types::list::List<NodeId>),
|
||||
DocumentNodeMetadata::SourceId => concrete!(u64),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +274,7 @@ impl NodeInput {
|
||||
NodeInput::Import { import_type, .. } => import_type.clone(),
|
||||
NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"),
|
||||
NodeInput::Scope(_) => panic!("ty() called on NodeInput::Scope"),
|
||||
NodeInput::Reflection(_) => concrete!(Metadata),
|
||||
NodeInput::Reflection(metadata) => metadata.ty(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,7 +880,7 @@ impl NodeNetwork {
|
||||
|
||||
// Replace value inputs with dedicated value nodes
|
||||
if node.implementation != DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("core_types::value::ClonedNode")) {
|
||||
Self::replace_value_inputs_with_nodes(&mut node.inputs, &mut self.nodes, &path, gen_id, map_ids, id);
|
||||
Self::replace_value_inputs_with_nodes(&mut node.inputs, &mut self.nodes, &path, gen_id, map_ids, id, Some(&mut node.context_features));
|
||||
}
|
||||
|
||||
let DocumentNodeImplementation::Network(mut inner_network) = node.implementation else {
|
||||
@@ -898,6 +899,7 @@ impl NodeNetwork {
|
||||
gen_id,
|
||||
map_ids,
|
||||
id,
|
||||
None,
|
||||
);
|
||||
|
||||
// Connect all network inputs to either the parent network nodes, or newly created value nodes for the parent node.
|
||||
@@ -978,6 +980,12 @@ impl NodeNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
fn source_id_for_path(path: &[NodeId]) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
path.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn replace_value_inputs_with_nodes(
|
||||
inputs: &mut [NodeInput],
|
||||
@@ -986,6 +994,7 @@ impl NodeNetwork {
|
||||
gen_id: impl Fn() -> NodeId + Copy,
|
||||
map_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy,
|
||||
id: NodeId,
|
||||
mut context_features: Option<&mut ContextDependencies>,
|
||||
) {
|
||||
// Replace value exports and imports with value nodes, added inside the nested network
|
||||
for export in inputs {
|
||||
@@ -996,6 +1005,13 @@ impl NodeNetwork {
|
||||
NodeInput::Value { tagged_value, exposed } => (tagged_value, exposed),
|
||||
NodeInput::Reflection(reflect) => match reflect {
|
||||
DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodeIdPath(path.to_vec()).into(), false),
|
||||
DocumentNodeMetadata::SourceId => {
|
||||
let source_id = Self::source_id_for_path(path);
|
||||
if let Some(context_features) = context_features.as_deref_mut() {
|
||||
core_types::context::merge_sorted_sources(&mut context_features.sources, &[source_id]);
|
||||
}
|
||||
(TaggedValue::U64(source_id).into(), false)
|
||||
}
|
||||
},
|
||||
previous_export => {
|
||||
*export = previous_export;
|
||||
|
||||
@@ -12,7 +12,7 @@ use core_types::gnode::GNode;
|
||||
use core_types::gpoll::GPoll;
|
||||
use core_types::registry::{EdgeHandle, edge_type};
|
||||
use core_types::value::value_edge;
|
||||
use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type, TypeDescriptor};
|
||||
use core_types::{CacheHash, Color, ContextModification, MemoHash, Node, Type, TypeDescriptor};
|
||||
use dyn_any::DynAny;
|
||||
pub use dyn_any::StaticType;
|
||||
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||
@@ -95,7 +95,7 @@ macro_rules! tagged_value {
|
||||
DocumentNode(DocumentNode),
|
||||
/// Carried by context nullification proto nodes constructed at proto node compilation time in `insert_context_nullification_nodes`.
|
||||
#[serde(skip)]
|
||||
ContextFeatures(ContextFeatures),
|
||||
ContextModification(ContextModification),
|
||||
#[serde(skip)]
|
||||
EditorApi(Arc<PlatformEditorApi>),
|
||||
/// Only used by the `resource` node, should never be serialized
|
||||
@@ -125,7 +125,7 @@ macro_rules! tagged_value {
|
||||
// =======================
|
||||
Self::NodeIdPath(path) => path.hash(state),
|
||||
Self::DocumentNode(node) => node.cache_hash(state),
|
||||
Self::ContextFeatures(features) => features.cache_hash(state),
|
||||
Self::ContextModification(modification) => modification.cache_hash(state),
|
||||
Self::RenderOutput(x) => x.cache_hash(state),
|
||||
Self::EditorApi(x) => x.cache_hash(state),
|
||||
Self::ResourceHash(x) => x.cache_hash(state),
|
||||
@@ -180,7 +180,7 @@ macro_rules! tagged_value {
|
||||
Box::new(list)
|
||||
}
|
||||
Self::DocumentNode(node) => Box::new(node),
|
||||
Self::ContextFeatures(features) => Box::new(features),
|
||||
Self::ContextModification(modification) => Box::new(modification),
|
||||
Self::EditorApi(x) => Box::new(x),
|
||||
Self::ResourceHash(x) => Box::new(x),
|
||||
}
|
||||
@@ -230,7 +230,7 @@ macro_rules! tagged_value {
|
||||
Arc::new(list)
|
||||
}
|
||||
Self::DocumentNode(node) => Arc::new(node),
|
||||
Self::ContextFeatures(features) => Arc::new(features),
|
||||
Self::ContextModification(modification) => Arc::new(modification),
|
||||
Self::EditorApi(x) => Arc::new(x),
|
||||
Self::ResourceHash(x) => Arc::new(x),
|
||||
}
|
||||
@@ -258,7 +258,7 @@ macro_rules! tagged_value {
|
||||
Self::RenderOutput(_) => concrete!(RenderOutput),
|
||||
Self::NodeIdPath(_) => concrete!(List<NodeId>),
|
||||
Self::DocumentNode(_) => concrete!(DocumentNode),
|
||||
Self::ContextFeatures(_) => concrete!(ContextFeatures),
|
||||
Self::ContextModification(_) => concrete!(ContextModification),
|
||||
Self::EditorApi(_) => concrete!(Arc<PlatformEditorApi>),
|
||||
Self::ResourceHash(_) => concrete!(ResourceHash),
|
||||
}
|
||||
@@ -308,7 +308,7 @@ macro_rules! tagged_value {
|
||||
Ok(value_edge(list))
|
||||
}
|
||||
Self::DocumentNode(node) => Ok(value_edge(node)),
|
||||
Self::ContextFeatures(features) => Ok(value_edge(features)),
|
||||
Self::ContextModification(modification) => Ok(value_edge(modification)),
|
||||
Self::EditorApi(x) => Ok(value_edge(x)),
|
||||
Self::ResourceHash(x) => Ok(value_edge(x)),
|
||||
}
|
||||
@@ -441,7 +441,7 @@ macro_rules! tagged_value {
|
||||
Self::RenderOutput(_) => "RenderOutput".to_string(),
|
||||
Self::NodeIdPath(path) => format!("NodeIdPath({path:?})"),
|
||||
Self::DocumentNode(node) => format!("DocumentNode({node:?})"),
|
||||
Self::ContextFeatures(features) => format!("ContextFeatures({features:?})"),
|
||||
Self::ContextModification(modification) => format!("ContextModification({modification:?})"),
|
||||
Self::EditorApi(_) => "PlatformEditorApi".to_string(),
|
||||
Self::ResourceHash(hash) => format!("ResourceHash({hash:?})"),
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ impl ProtoNetwork {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_context_nullification_node(&mut self, node_id: NodeId, context_deps: ContextFeatures) -> NodeId {
|
||||
fn insert_context_nullification_node(&mut self, node_id: NodeId, context_deps: ContextModification) -> NodeId {
|
||||
let (_, node) = &self.nodes[node_id.0 as usize];
|
||||
let mut path = node.original_location.path.clone();
|
||||
|
||||
@@ -338,7 +338,7 @@ impl ProtoNetwork {
|
||||
self.nodes.push((
|
||||
nullification_value_node_id,
|
||||
ProtoNode {
|
||||
construction_args: ConstructionArgs::Value(MemoHash::new(TaggedValue::ContextFeatures(context_deps))),
|
||||
construction_args: ConstructionArgs::Value(MemoHash::new(TaggedValue::ContextModification(context_deps))),
|
||||
call_argument: concrete!(Context),
|
||||
identifier: ProtoNodeIdentifier::new("core_types::value::ClonedNode"),
|
||||
original_location: OriginalLocation {
|
||||
@@ -365,36 +365,40 @@ impl ProtoNetwork {
|
||||
nullification_node_id
|
||||
}
|
||||
|
||||
fn find_context_dependencies(&mut self, id: NodeId) -> (ContextFeatures, Option<NodeId>) {
|
||||
fn find_context_dependencies(&mut self, id: NodeId) -> (ContextModification, Option<NodeId>) {
|
||||
let mut branch_dependencies = Vec::new();
|
||||
let mut combined_deps = ContextFeatures::default();
|
||||
let mut combined_deps = ContextModification::default();
|
||||
let node_index = id.0 as usize;
|
||||
|
||||
let context_features = self.nodes[node_index].1.context_features;
|
||||
let context_features = self.nodes[node_index].1.context_features.clone();
|
||||
let own_deps = ContextModification {
|
||||
features: context_features.extract,
|
||||
sources: context_features.sources.clone(),
|
||||
};
|
||||
|
||||
let mut inputs = match &self.nodes[node_index].1.construction_args {
|
||||
// We pretend like we have already placed context modification nodes after ourselves because value nodes don't need to be cached
|
||||
ConstructionArgs::Value(_) => return (context_features.extract, Some(id)),
|
||||
ConstructionArgs::Value(_) => return (own_deps, Some(id)),
|
||||
ConstructionArgs::Nodes(items) => items.clone(),
|
||||
ConstructionArgs::Inline(_) => return (context_features.extract, Some(id)),
|
||||
ConstructionArgs::Inline(_) => return (own_deps, Some(id)),
|
||||
};
|
||||
|
||||
// Compute the dependencies for each branch and combine all of them
|
||||
for &node in &inputs {
|
||||
let branch = self.find_context_dependencies(node);
|
||||
|
||||
combined_deps |= &branch.0;
|
||||
branch_dependencies.push(branch);
|
||||
combined_deps |= branch.0;
|
||||
}
|
||||
let mut new_deps = combined_deps;
|
||||
let mut new_deps = combined_deps.clone();
|
||||
|
||||
// Remove requirements which this node provides
|
||||
new_deps &= !context_features.inject;
|
||||
// Add requirements we have
|
||||
new_deps |= context_features.extract;
|
||||
new_deps |= own_deps;
|
||||
|
||||
// If we either introduce new dependencies, we can cache all children which don't yet need that dependency
|
||||
let we_introduce_new_deps = !combined_deps.contains(new_deps);
|
||||
let we_introduce_new_deps = !combined_deps.contains(&new_deps);
|
||||
|
||||
// For diverging branches, we can add a cache node for all branches which don't reqire all dependencies
|
||||
for (child_node, (deps, new_id)) in inputs.iter_mut().zip(branch_dependencies.into_iter()) {
|
||||
@@ -410,15 +414,15 @@ impl ProtoNetwork {
|
||||
let net_injections = context_features.inject.difference(context_features.extract);
|
||||
|
||||
// Which dependencies still need to be met after this node?
|
||||
let remaining_deps_from_children = combined_deps.difference(net_injections);
|
||||
let remaining_deps_from_children = combined_deps.features.difference(net_injections);
|
||||
|
||||
// Do we satisfy any existing dependencies?
|
||||
let we_supply_existing_deps = !combined_deps.difference(remaining_deps_from_children).is_empty();
|
||||
let we_supply_existing_deps = !combined_deps.features.difference(remaining_deps_from_children).is_empty();
|
||||
|
||||
let mut new_id = None;
|
||||
if we_supply_existing_deps {
|
||||
// Our set of context dependencies has shrunk so we can add a cache node after the current node
|
||||
new_id = Some(self.insert_context_nullification_node(id, new_deps));
|
||||
new_id = Some(self.insert_context_nullification_node(id, new_deps.clone()));
|
||||
}
|
||||
|
||||
(new_deps, new_id)
|
||||
|
||||
@@ -188,18 +188,18 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
||||
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]),
|
||||
// Context nullification
|
||||
#[cfg(feature = "gpu")]
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc<PlatformEditorApi>, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeDyn, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => ListDyn, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc<PlatformEditorApi>, Context => graphene_std::ContextModification]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextModification]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextModification]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeDyn, Context => graphene_std::ContextModification]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn, Context => graphene_std::ContextModification]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => ListDyn, Context => graphene_std::ContextModification]),
|
||||
#[cfg(target_family = "wasm")]
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc<PlatformEditorApi>, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuExecutorHandle, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Option<wgpu_executor::WgpuExecutorHandle>, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuPipelineCache, Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextModification]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc<PlatformEditorApi>, Context => graphene_std::ContextModification]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuExecutorHandle, Context => graphene_std::ContextModification]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Option<wgpu_executor::WgpuExecutorHandle>, Context => graphene_std::ContextModification]),
|
||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuPipelineCache, Context => graphene_std::ContextModification]),
|
||||
// ==========
|
||||
// MEMO NODES
|
||||
// ==========
|
||||
@@ -247,7 +247,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),
|
||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<BrushStroke>]),
|
||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DocumentNode]),
|
||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]),
|
||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextModification]),
|
||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]),
|
||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Box<graphene_std::vector::VectorModification>]),
|
||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]),
|
||||
|
||||
@@ -33,6 +33,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc<PlatformEdito
|
||||
context_features: graphene_std::ContextDependencies {
|
||||
extract: ContextFeatures::VARARGS,
|
||||
inject: ContextFeatures::INDEX,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
@@ -43,6 +44,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc<PlatformEdito
|
||||
context_features: graphene_std::ContextDependencies {
|
||||
extract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
|
||||
inject: ContextFeatures::empty(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
@@ -57,6 +59,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc<PlatformEdito
|
||||
context_features: graphene_std::ContextDependencies {
|
||||
extract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
|
||||
inject: ContextFeatures::VARARGS,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
@@ -67,6 +70,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc<PlatformEdito
|
||||
context_features: graphene_std::ContextDependencies {
|
||||
extract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
|
||||
inject: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
@@ -80,6 +84,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc<PlatformEdito
|
||||
context_features: graphene_std::ContextDependencies {
|
||||
extract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
|
||||
inject: ContextFeatures::empty(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
@@ -91,6 +96,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc<PlatformEdito
|
||||
// We add the extract index annotation here to force the compiler to add a context nullification node before this node so the render context is properly nullified so the render cache node can do its's work
|
||||
extract: ContextFeatures::INDEX | ContextFeatures::VARARGS,
|
||||
inject: ContextFeatures::REAL_TIME | ContextFeatures::ANIMATION_TIME | ContextFeatures::POINTER_POSITION | ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -194,11 +194,72 @@ impl ContextFeatures {
|
||||
// CONTEXT DEPENDENCIES
|
||||
// ====================
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash, dyn_any::DynAny, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, graphene_hash::CacheHash, dyn_any::DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ContextDependencies {
|
||||
pub extract: ContextFeatures,
|
||||
pub inject: ContextFeatures,
|
||||
/// Must stay sorted.
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub sources: Vec<SourceId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, graphene_hash::CacheHash, dyn_any::DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ContextModification {
|
||||
pub features: ContextFeatures,
|
||||
/// Must stay sorted.
|
||||
pub sources: Vec<SourceId>,
|
||||
}
|
||||
|
||||
impl core::ops::BitOrAssign<&ContextModification> for ContextModification {
|
||||
fn bitor_assign(&mut self, other: &Self) {
|
||||
self.features |= other.features;
|
||||
merge_sorted_sources(&mut self.sources, &other.sources);
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::BitOrAssign for ContextModification {
|
||||
fn bitor_assign(&mut self, other: Self) {
|
||||
*self |= &other;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::BitOrAssign<ContextFeatures> for ContextModification {
|
||||
fn bitor_assign(&mut self, features: ContextFeatures) {
|
||||
self.features |= features;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::BitAndAssign<ContextFeatures> for ContextModification {
|
||||
fn bitand_assign(&mut self, features: ContextFeatures) {
|
||||
self.features &= features;
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextModification {
|
||||
pub fn contains(&self, other: &Self) -> bool {
|
||||
self.features.contains(other.features) && other.sources.iter().all(|id| self.sources.binary_search(id).is_ok())
|
||||
}
|
||||
|
||||
pub fn difference(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
features: self.features.difference(other.features),
|
||||
sources: self.sources.iter().copied().filter(|id| other.sources.binary_search(id).is_err()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.features.is_empty() && self.sources.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge_sorted_sources(sources: &mut Vec<SourceId>, other: &[SourceId]) {
|
||||
for &id in other {
|
||||
if let Err(insert_at) = sources.binary_search(&id) {
|
||||
sources.insert(insert_at, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[ContextFeature]> for ContextDependencies {
|
||||
@@ -227,7 +288,11 @@ impl From<&[ContextFeature]> for ContextDependencies {
|
||||
_ => ContextFeatures::empty(),
|
||||
};
|
||||
}
|
||||
Self { extract, inject }
|
||||
Self {
|
||||
extract,
|
||||
inject,
|
||||
sources: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,53 +812,52 @@ impl<'a> EvalScope<'a> {
|
||||
arena,
|
||||
hash: 0,
|
||||
};
|
||||
scope.hash = scope.compute_hash(None);
|
||||
scope.hash = scope.compute_hash(|_| true);
|
||||
scope
|
||||
}
|
||||
|
||||
pub fn retained(&self, retain: &[SourceId]) -> EvalScope<'a> {
|
||||
EvalScope {
|
||||
hash: self.compute_hash(Some(retain)),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_real_time(&self, real_time: Option<f64>) -> EvalScope<'a> {
|
||||
let mut scope = EvalScope { real_time, ..*self };
|
||||
scope.hash = scope.compute_hash(None);
|
||||
scope.hash = scope.compute_hash(|_| true);
|
||||
scope
|
||||
}
|
||||
|
||||
pub fn with_animation_time(&self, animation_time: Option<f64>) -> EvalScope<'a> {
|
||||
let mut scope = EvalScope { animation_time, ..*self };
|
||||
scope.hash = scope.compute_hash(None);
|
||||
scope.hash = scope.compute_hash(|_| true);
|
||||
scope
|
||||
}
|
||||
|
||||
pub fn with_pointer_position(&self, pointer_position: Option<DVec2>) -> EvalScope<'a> {
|
||||
let mut scope = EvalScope { pointer_position, ..*self };
|
||||
scope.hash = scope.compute_hash(None);
|
||||
scope.hash = scope.compute_hash(|_| true);
|
||||
scope
|
||||
}
|
||||
|
||||
pub fn nullified(&self, keep: ContextFeatures) -> EvalScope<'a> {
|
||||
pub fn nullified(&self, keep: ContextFeatures, retain: Option<&[SourceId]>) -> EvalScope<'a> {
|
||||
let mut scope = EvalScope {
|
||||
real_time: self.real_time.filter(|_| keep.contains(ContextFeatures::REAL_TIME)),
|
||||
animation_time: self.animation_time.filter(|_| keep.contains(ContextFeatures::ANIMATION_TIME)),
|
||||
pointer_position: self.pointer_position.filter(|_| keep.contains(ContextFeatures::POINTER_POSITION)),
|
||||
..*self
|
||||
};
|
||||
scope.hash = scope.compute_hash(None);
|
||||
scope.hash = scope.compute_hash(|source| retain.is_none_or(|retain| retain.contains(source)));
|
||||
scope
|
||||
}
|
||||
|
||||
fn compute_hash(&self, retain: Option<&[SourceId]>) -> u64 {
|
||||
pub fn excluding(&self, source: SourceId) -> EvalScope<'a> {
|
||||
let mut scope = *self;
|
||||
scope.hash = scope.compute_hash(|candidate| *candidate != source);
|
||||
scope
|
||||
}
|
||||
|
||||
fn compute_hash(&self, keep_source: impl Fn(&SourceId) -> bool) -> u64 {
|
||||
let mut hasher = std::hash::DefaultHasher::new();
|
||||
self.real_time.map(f64::to_bits).hash(&mut hasher);
|
||||
self.animation_time.map(f64::to_bits).hash(&mut hasher);
|
||||
self.pointer_position.map(|position| (position.x.to_bits(), position.y.to_bits())).hash(&mut hasher);
|
||||
for (source, generation) in self.generations {
|
||||
if retain.is_none_or(|retain| retain.contains(source)) {
|
||||
if keep_source(source) {
|
||||
(source, generation).hash(&mut hasher);
|
||||
}
|
||||
}
|
||||
@@ -1360,7 +1424,7 @@ mod context_impl_tests {
|
||||
let bumped_retained = [(0, 1), (1, 4)];
|
||||
|
||||
let hash_with = |generations: &[(SourceId, u64)]| {
|
||||
let scope = scope_fixture(generations, &arena).retained(&[1]);
|
||||
let scope = scope_fixture(generations, &arena).nullified(ContextFeatures::all(), Some(&[1]));
|
||||
let retained_scope_context = ContextImpl::root(&scope);
|
||||
hash_of(&retained_scope_context)
|
||||
};
|
||||
|
||||
@@ -58,6 +58,7 @@ pub enum RegistryValueSource {
|
||||
None,
|
||||
Default(&'static str),
|
||||
Scope(&'static str),
|
||||
SourceId,
|
||||
}
|
||||
|
||||
type NodeRegistry = LazyLock<Mutex<HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>>>>;
|
||||
|
||||
@@ -409,8 +409,7 @@ mod tests {
|
||||
|
||||
let snapshot = runtime.snapshot();
|
||||
let scope = EvalScope::new(None, None, None, &snapshot, &arena);
|
||||
let source_scope = scope.retained(&[]);
|
||||
let ctx = ContextImpl::root(&source_scope);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending);
|
||||
assert!(!runtime.take_dirty());
|
||||
|
||||
@@ -420,9 +419,8 @@ mod tests {
|
||||
assert_eq!(bumped, vec![(11, 1)]);
|
||||
|
||||
let bumped_scope = EvalScope::new(None, None, None, &bumped, &arena);
|
||||
let bumped_source_scope = bumped_scope.retained(&[]);
|
||||
let bumped_ctx = ContextImpl::root(&bumped_source_scope);
|
||||
assert_eq!(GNode::eval(&graph, &bumped_ctx), GPoll::Final(42.0), "the retained key replays the landed slot");
|
||||
let bumped_ctx = ContextImpl::root(&bumped_scope);
|
||||
assert_eq!(GNode::eval(&graph, &bumped_ctx), GPoll::Final(42.0), "the own-generation-excluded key replays the landed slot");
|
||||
assert_eq!(runtime.spawner().drain(), 0, "a slot hit must not respawn");
|
||||
|
||||
let downstream_key = crate::registry::cache_key(&ContextImpl::root(&scope));
|
||||
|
||||
@@ -149,6 +149,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
quote!(RegistryValueSource::Scope(#data.as_static_str()))
|
||||
}
|
||||
}
|
||||
ParsedValueSource::SourceId => quote!(RegistryValueSource::SourceId),
|
||||
_ => quote!(RegistryValueSource::None),
|
||||
},
|
||||
_ => quote!(RegistryValueSource::None),
|
||||
|
||||
@@ -40,8 +40,8 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF
|
||||
.collect(),
|
||||
None => vec![quote!(#core_types::Ctx)],
|
||||
};
|
||||
if async_source {
|
||||
ctx_bounds.push(quote!(#core_types::CacheHash));
|
||||
if async_source && !snapshot_ctx {
|
||||
ctx_bounds.push(quote!(#core_types::context::DeriveCtx));
|
||||
}
|
||||
if snapshot_ctx {
|
||||
ctx_bounds.extend([
|
||||
@@ -144,7 +144,7 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF
|
||||
}
|
||||
});
|
||||
|
||||
let async_bounds = match (async_fn, future_kernel) {
|
||||
let mut async_bounds = match (async_fn, future_kernel) {
|
||||
(false, false) => Vec::new(),
|
||||
(false, true) => vec![quote!(#trait_output: Clone)],
|
||||
(true, _) => {
|
||||
@@ -160,6 +160,9 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF
|
||||
output_clone.chain(value_clones).chain(data_clones).collect()
|
||||
}
|
||||
};
|
||||
if async_source {
|
||||
async_bounds.push(quote!(for<'__derived> #core_types::context::Derived<'__derived, #ctx_ident>: #core_types::CacheHash));
|
||||
}
|
||||
|
||||
let clampable_bounds = regular_fields.iter().filter_map(|field| {
|
||||
let ParsedFieldType::Regular(RegularParsedField { ty, number_hard_min, number_hard_max, .. }) = &field.ty else {
|
||||
@@ -326,7 +329,8 @@ pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeF
|
||||
None => quote!(#core_types::gpoll::GPoll::Pending),
|
||||
};
|
||||
let slot_check = quote! {
|
||||
let __key = #core_types::registry::cache_key(__input);
|
||||
let __scope = #core_types::context::DeriveCtx::scope(__input).excluding(_source);
|
||||
let __key = #core_types::registry::cache_key(&#core_types::context::DeriveCtx::with_scope(__input, &__scope));
|
||||
{
|
||||
let __entries = self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(__state) = __entries.get(&__key) {
|
||||
|
||||
@@ -72,6 +72,7 @@ pub enum ParsedValueSource {
|
||||
None,
|
||||
Default(TokenStream2),
|
||||
Scope(Expr),
|
||||
SourceId,
|
||||
}
|
||||
|
||||
// #[widget(ParsedWidgetOverride::Hidden)]
|
||||
@@ -1063,7 +1064,7 @@ impl ParsedNodeFn {
|
||||
parse_quote!(#core_types::runtime::RuntimeHandle),
|
||||
ParsedValueSource::Scope(parse_quote!("graphene_std::runtime::RuntimeNode")),
|
||||
));
|
||||
self.fields.push(hidden_field("_source", parse_quote!(#core_types::SourceId), ParsedValueSource::None));
|
||||
self.fields.push(hidden_field("_source", parse_quote!(#core_types::SourceId), ParsedValueSource::SourceId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use core::f64;
|
||||
use core_types::context::{Context, ContextFeatures, Ctx, DeriveCtx};
|
||||
use core_types::context::{Context, ContextModification, Ctx, DeriveCtx};
|
||||
use core_types::gpoll::GPoll;
|
||||
use core_types::list::{AttributeDyn, AttributeValueDyn, List, ListDyn};
|
||||
use core_types::transform::Footprint;
|
||||
@@ -44,8 +44,8 @@ fn context_modification<T>(
|
||||
)]
|
||||
value: impl Node<Context<'_>, Output = T>,
|
||||
/// The parts of the context to keep when evaluating the input value. All other parts are nullified.
|
||||
features_to_keep: ContextFeatures,
|
||||
modification: ContextModification,
|
||||
) -> GPoll<T> {
|
||||
let scope = ctx.scope().nullified(features_to_keep);
|
||||
value.eval(&ctx.nullified(features_to_keep, &scope))
|
||||
let scope = ctx.scope().nullified(modification.features, Some(&modification.sources));
|
||||
value.eval(&ctx.nullified(modification.features, &scope))
|
||||
}
|
||||
|
||||
@@ -279,6 +279,7 @@ pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTyp
|
||||
}
|
||||
}
|
||||
RegistryValueSource::Scope(data) => return NodeInput::scope(*data),
|
||||
RegistryValueSource::SourceId => return NodeInput::Reflection(DocumentNodeMetadata::SourceId),
|
||||
};
|
||||
|
||||
if let Some(type_default) = TaggedValue::from_type(ty) {
|
||||
|
||||
Reference in New Issue
Block a user