mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 20:28:12 +08:00
Convert values to typed edges and flip the interpreted executor
This commit is contained in:
@@ -3,7 +3,6 @@ use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
|||||||
use glam::{DAffine2, DVec2, UVec2};
|
use glam::{DAffine2, DVec2, UVec2};
|
||||||
use graph_craft::application_io::resource::ResourceRegistry;
|
use graph_craft::application_io::resource::ResourceRegistry;
|
||||||
use graph_craft::application_io::{PlatformApplicationIo, PlatformEditorApi};
|
use graph_craft::application_io::{PlatformApplicationIo, PlatformEditorApi};
|
||||||
use graph_craft::concrete;
|
|
||||||
use graph_craft::document::value::{RenderOutput, RenderOutputType, TaggedValue};
|
use graph_craft::document::value::{RenderOutput, RenderOutputType, TaggedValue};
|
||||||
use graph_craft::document::{NodeId, NodeNetwork};
|
use graph_craft::document::{NodeId, NodeNetwork};
|
||||||
use graph_craft::graphene_compiler::Compiler;
|
use graph_craft::graphene_compiler::Compiler;
|
||||||
@@ -12,7 +11,7 @@ use graphene_std::application_io::{ApplicationIo, ExportFormat, NodeGraphUpdateM
|
|||||||
use graphene_std::bounds::RenderBoundingBox;
|
use graphene_std::bounds::RenderBoundingBox;
|
||||||
use graphene_std::list::List;
|
use graphene_std::list::List;
|
||||||
use graphene_std::memo::IORecord;
|
use graphene_std::memo::IORecord;
|
||||||
use graphene_std::ops::Convert;
|
use graphene_std::ops::{Convert, ConvertAsync};
|
||||||
#[cfg(all(target_family = "wasm", feature = "gpu", feature = "wasm"))]
|
#[cfg(all(target_family = "wasm", feature = "gpu", feature = "wasm"))]
|
||||||
use graphene_std::platform_application_io::canvas_utils::{Canvas, CanvasSurface, CanvasSurfaceHandle};
|
use graphene_std::platform_application_io::canvas_utils::{Canvas, CanvasSurface, CanvasSurfaceHandle};
|
||||||
use graphene_std::raster_types::Raster;
|
use graphene_std::raster_types::Raster;
|
||||||
@@ -377,12 +376,11 @@ impl NodeRuntime {
|
|||||||
async fn execute_network(&mut self, render_config: RenderConfig) -> Result<TaggedValue, String> {
|
async fn execute_network(&mut self, render_config: RenderConfig) -> Result<TaggedValue, String> {
|
||||||
use graph_craft::graphene_compiler::Executor;
|
use graph_craft::graphene_compiler::Executor;
|
||||||
|
|
||||||
match self.executor.input_type() {
|
let (value, evaluation_error) = (&self.executor).execute(render_config).await.map_err(|e| e.to_string())?;
|
||||||
Some(t) if t == concrete!(RenderConfig) => (&self.executor).execute(render_config).await.map_err(|e| e.to_string()),
|
if let Some(error) = evaluation_error {
|
||||||
Some(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()),
|
error!("Node graph evaluation reported an error alongside its fallback output: {error:?}");
|
||||||
Some(t) => Err(format!("Invalid input type {t:?}")),
|
|
||||||
_ => Err(format!("No input type:\n{:?}", self.node_graph_errors)),
|
|
||||||
}
|
}
|
||||||
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates state data
|
/// Updates state data
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ use core_types::color::SRGBA8;
|
|||||||
use core_types::list::List;
|
use core_types::list::List;
|
||||||
use core_types::transform::Footprint;
|
use core_types::transform::Footprint;
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
|
use core_types::context::Context;
|
||||||
|
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, ContextFeatures, MemoHash, Node, Type, TypeDescriptor};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
pub use dyn_any::StaticType;
|
pub use dyn_any::StaticType;
|
||||||
@@ -259,6 +264,82 @@ macro_rules! tagged_value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Materializes the value exactly as [`Self::to_dynany`] does and wraps it in a `ClonedNode` behind an [`EdgeHandle`], whose type matches [`Self::ty`].
|
||||||
|
pub fn to_edge(self) -> Result<EdgeHandle, String> {
|
||||||
|
match self {
|
||||||
|
// ===============
|
||||||
|
// MANUAL VARIANTS
|
||||||
|
// ===============
|
||||||
|
Self::None => Ok(value_edge(())),
|
||||||
|
Self::TypeDefault(td) => {
|
||||||
|
// Same direct-construction path as `to_dynany` for the same reason as in `to_dynany`.
|
||||||
|
let name = td.name.as_ref();
|
||||||
|
macro_rules! check {
|
||||||
|
($type_default:ty) => {
|
||||||
|
if name == std::any::type_name::<$type_default>() { return Ok(value_edge(<$type_default>::default())); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for_each_type_default!(check);
|
||||||
|
Self::from_type_or_none(&Type::Concrete(td)).to_edge()
|
||||||
|
}
|
||||||
|
Self::F64Array(values) => {
|
||||||
|
let list: List<f64> = values.into_iter().map(core_types::list::Item::new_from_element).collect();
|
||||||
|
Ok(value_edge(list))
|
||||||
|
}
|
||||||
|
Self::Color(color) => {
|
||||||
|
let list: List<Color> = color.into_iter().map(core_types::list::Item::new_from_element).collect();
|
||||||
|
Ok(value_edge(list))
|
||||||
|
}
|
||||||
|
Self::Gradient(stops) => Ok(value_edge(List::<GradientStops>::new_from_element(stops))),
|
||||||
|
Self::BrushStrokes(strokes) => {
|
||||||
|
let list: List<BrushStroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
|
||||||
|
Ok(value_edge(list))
|
||||||
|
}
|
||||||
|
// =======================
|
||||||
|
// AUTO-GENERATED VARIANTS
|
||||||
|
// =======================
|
||||||
|
$( Self::$identifier(x) => Ok(value_edge(x)), )*
|
||||||
|
// =======================
|
||||||
|
// NON-SERIALIZED VARIANTS
|
||||||
|
// =======================
|
||||||
|
Self::RenderOutput(x) => Ok(value_edge(x)),
|
||||||
|
Self::NodeIdPath(path) => {
|
||||||
|
let list: List<NodeId> = path.into_iter().map(core_types::list::Item::new_from_element).collect();
|
||||||
|
Ok(value_edge(list))
|
||||||
|
}
|
||||||
|
Self::DocumentNode(node) => Ok(value_edge(node)),
|
||||||
|
Self::ContextFeatures(features) => Ok(value_edge(features)),
|
||||||
|
Self::EditorApi(_) => Err("EditorApi values are wired by the executor, not as value edges".to_string()),
|
||||||
|
Self::ResourceHash(x) => Ok(value_edge(x)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluates a typed edge and converts the landed value into a tagged value; the eval-boundary companion of [`Self::to_edge`] with the coverage of [`Self::try_from_any`].
|
||||||
|
pub fn from_edge(handle: EdgeHandle, ctx: &Context) -> Result<GPoll<Self>, String> {
|
||||||
|
let ty = handle.ty().clone();
|
||||||
|
// ===============
|
||||||
|
// MANUAL VARIANTS
|
||||||
|
// ===============
|
||||||
|
if ty == edge_type::<()>() {
|
||||||
|
return Ok(handle.downcast::<()>().map_err(|e| format!("{e:?}"))?.eval(ctx).map(|_| TaggedValue::None));
|
||||||
|
}
|
||||||
|
// =======================
|
||||||
|
// AUTO-GENERATED VARIANTS
|
||||||
|
// =======================
|
||||||
|
$(
|
||||||
|
if ty == edge_type::<$ty>() {
|
||||||
|
return Ok(handle.downcast::<$ty>().map_err(|e| format!("{e:?}"))?.eval(ctx).map(TaggedValue::$identifier));
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
// =======================
|
||||||
|
// NON-SERIALIZED VARIANTS
|
||||||
|
// =======================
|
||||||
|
if ty == edge_type::<RenderOutput>() {
|
||||||
|
return Ok(handle.downcast::<RenderOutput>().map_err(|e| format!("{e:?}"))?.eval(ctx).map(TaggedValue::RenderOutput));
|
||||||
|
}
|
||||||
|
Err(format!("Cannot convert edge of type {ty} to TaggedValue"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to downcast the dynamic type to a tagged value
|
/// Attempts to downcast the dynamic type to a tagged value
|
||||||
pub fn try_from_any(input: Box<dyn DynAny<'a> + 'a>) -> Result<Self, String> {
|
pub fn try_from_any(input: Box<dyn DynAny<'a> + 'a>) -> Result<Self, String> {
|
||||||
use dyn_any::downcast;
|
use dyn_any::downcast;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use graph_craft::document::value::{RenderOutputType, TaggedValue, UVec2};
|
use graph_craft::document::value::{RenderOutputType, TaggedValue, UVec2};
|
||||||
use graph_craft::graphene_compiler::Executor;
|
use graph_craft::graphene_compiler::Executor;
|
||||||
use graphene_std::application_io::{ExportFormat, RenderConfig, TimingInformation};
|
use graphene_std::application_io::{ExportFormat, RenderConfig, TimingInformation};
|
||||||
use graphene_std::core_types::ops::Convert;
|
use graphene_std::core_types::ops::{Convert, ConvertAsync};
|
||||||
use graphene_std::core_types::transform::Footprint;
|
use graphene_std::core_types::transform::Footprint;
|
||||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||||
@@ -57,7 +57,10 @@ pub async fn export_document(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute the graph
|
// Execute the graph
|
||||||
let result = executor.execute(render_config).await?;
|
let (result, evaluation_error) = executor.execute(render_config).await?;
|
||||||
|
if let Some(error) = evaluation_error {
|
||||||
|
log::error!("Node graph evaluation reported an error alongside its fallback output: {error:?}");
|
||||||
|
}
|
||||||
|
|
||||||
// Handle the result based on output type
|
// Handle the result based on output type
|
||||||
match result {
|
match result {
|
||||||
@@ -195,7 +198,10 @@ pub async fn export_gif(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute the graph for this frame
|
// Execute the graph for this frame
|
||||||
let result = executor.execute(render_config).await?;
|
let (result, evaluation_error) = executor.execute(render_config).await?;
|
||||||
|
if let Some(error) = evaluation_error {
|
||||||
|
log::error!("Node graph evaluation reported an error alongside its fallback output: {error:?}");
|
||||||
|
}
|
||||||
|
|
||||||
// Extract RGBA data from result
|
// Extract RGBA data from result
|
||||||
let (data, img_width, img_height) = match result {
|
let (data, img_width, img_height) = match result {
|
||||||
|
|||||||
@@ -1,17 +1,32 @@
|
|||||||
use crate::node_registry;
|
use crate::node_registry;
|
||||||
use dyn_any::StaticType;
|
use core_types::arena::Arena;
|
||||||
|
use core_types::context::{ContextImpl, DynSlot, EvalScope, VarArg, VarArgLink, VarArgSlots};
|
||||||
|
use core_types::gnode::GNode;
|
||||||
|
use core_types::gpoll::GPoll;
|
||||||
|
use core_types::registry::{EdgeHandle, ErasedGNode};
|
||||||
|
use core_types::runtime::{GraphRuntime, SourceFuture, Spawner};
|
||||||
use graph_craft::Type;
|
use graph_craft::Type;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode};
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::graphene_compiler::Executor;
|
use graph_craft::graphene_compiler::Executor;
|
||||||
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext};
|
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, ProtoNetwork, ProtoNode, TypingContext};
|
||||||
use graph_craft::proto::{GraphErrorType, GraphErrors};
|
use graph_craft::proto::{GraphErrorType, GraphErrors};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Mutex, PoisonError};
|
||||||
|
|
||||||
|
const ARENA_CAPACITY: usize = 1 << 20;
|
||||||
|
|
||||||
|
/// Dropped tasks never complete; replaced by the host spawner when the runtime scope wiring lands.
|
||||||
|
pub struct NoopSpawner;
|
||||||
|
|
||||||
|
impl Spawner for NoopSpawner {
|
||||||
|
fn spawn(&self, _task: SourceFuture) {
|
||||||
|
log::warn!("async source spawned before a host spawner is wired; the task is dropped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An executor of a node graph that does not require an online compilation server, and instead uses `Box<dyn ...>`.
|
/// An executor of a node graph that does not require an online compilation server, and instead uses `Box<dyn ...>`.
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct DynamicExecutor {
|
pub struct DynamicExecutor {
|
||||||
output: NodeId,
|
output: NodeId,
|
||||||
/// Stores all of the dynamic node structs.
|
/// Stores all of the dynamic node structs.
|
||||||
@@ -20,6 +35,8 @@ pub struct DynamicExecutor {
|
|||||||
typing_context: TypingContext,
|
typing_context: TypingContext,
|
||||||
// This allows us to keep the nodes around for one more frame which is used for introspection
|
// This allows us to keep the nodes around for one more frame which is used for introspection
|
||||||
orphaned_nodes: HashSet<NodeId>,
|
orphaned_nodes: HashSet<NodeId>,
|
||||||
|
arena: Mutex<Arena>,
|
||||||
|
runtime: Arc<GraphRuntime<NoopSpawner>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DynamicExecutor {
|
impl Default for DynamicExecutor {
|
||||||
@@ -29,6 +46,8 @@ impl Default for DynamicExecutor {
|
|||||||
tree: Default::default(),
|
tree: Default::default(),
|
||||||
typing_context: TypingContext::new(&node_registry::NODE_REGISTRY),
|
typing_context: TypingContext::new(&node_registry::NODE_REGISTRY),
|
||||||
orphaned_nodes: HashSet::new(),
|
orphaned_nodes: HashSet::new(),
|
||||||
|
arena: Mutex::new(Arena::new(ARENA_CAPACITY)),
|
||||||
|
runtime: Arc::new(GraphRuntime::new(NoopSpawner)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +78,8 @@ impl DynamicExecutor {
|
|||||||
output,
|
output,
|
||||||
typing_context,
|
typing_context,
|
||||||
orphaned_nodes: HashSet::new(),
|
orphaned_nodes: HashSet::new(),
|
||||||
|
arena: Mutex::new(Arena::new(ARENA_CAPACITY)),
|
||||||
|
runtime: Arc::new(GraphRuntime::new(NoopSpawner)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,27 +156,51 @@ impl DynamicExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I> Executor<I, TaggedValue> for &DynamicExecutor
|
impl<I> Executor<I, (TaggedValue, Option<core_types::gpoll::GraphError>)> for &DynamicExecutor
|
||||||
where
|
where
|
||||||
I: StaticType + 'static + Send + Sync + std::panic::UnwindSafe,
|
I: VarArg + Send + Sync + std::panic::RefUnwindSafe,
|
||||||
{
|
{
|
||||||
fn execute(&self, input: I) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>> {
|
fn execute(&self, input: I) -> LocalFuture<'_, Result<(TaggedValue, Option<core_types::gpoll::GraphError>), Box<dyn Error>>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
use futures::FutureExt;
|
let Some(handle) = self.tree.get(self.output) else {
|
||||||
|
return Err("Output node not found in executor".into());
|
||||||
let result = self.tree.eval_tagged_value(self.output, input);
|
};
|
||||||
let wrapped_result = std::panic::AssertUnwindSafe(result).catch_unwind().await;
|
let mut arena = self.arena.lock().unwrap_or_else(PoisonError::into_inner);
|
||||||
|
let result = eval_root(&mut arena, &self.runtime, &input, |ctx| match TaggedValue::from_edge(handle.duplicate(), ctx) {
|
||||||
match wrapped_result {
|
Ok(poll) => poll.map(Ok),
|
||||||
Ok(result) => result.map_err(|e| e.into()),
|
Err(error) => GPoll::Final(Err(error)),
|
||||||
Err(e) => {
|
});
|
||||||
Box::leak(e);
|
match result {
|
||||||
Err("Node graph execution panicked".into())
|
GPoll::Final(value) | GPoll::Partial(value) => Ok((value?, None)),
|
||||||
|
GPoll::Fallback(boxed) => {
|
||||||
|
let (value, error) = *boxed;
|
||||||
|
Ok((value?, Some(error)))
|
||||||
}
|
}
|
||||||
|
GPoll::Pending => Err("Node graph evaluation is pending".into()),
|
||||||
|
GPoll::Error(error) => Err(format!("Node graph evaluation failed: {error:?}").into()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn eval_root<S, T>(arena: &mut Arena, runtime: &GraphRuntime<S>, call_argument: DynSlot, eval: impl FnOnce(&ContextImpl) -> GPoll<T>) -> GPoll<T> {
|
||||||
|
arena.reset();
|
||||||
|
let generations = runtime.snapshot();
|
||||||
|
let scope = EvalScope::new(None, None, None, &generations, arena);
|
||||||
|
let root = ContextImpl::root(&scope);
|
||||||
|
let link = VarArgLink {
|
||||||
|
args: VarArgSlots::Single(call_argument),
|
||||||
|
outer: None,
|
||||||
|
};
|
||||||
|
let ctx = root.with_varargs(&link);
|
||||||
|
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| eval(&ctx))) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => {
|
||||||
|
arena.reset();
|
||||||
|
GPoll::panicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct InputMapping {}
|
pub struct InputMapping {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
@@ -195,10 +240,10 @@ impl std::fmt::Display for IntrospectError {
|
|||||||
/// This maps document paths to node IDs and their associated type information.
|
/// This maps document paths to node IDs and their associated type information.
|
||||||
///
|
///
|
||||||
/// A store of the dynamically typed nodes and also the source map.
|
/// A store of the dynamically typed nodes and also the source map.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default)]
|
||||||
pub struct BorrowTree {
|
pub struct BorrowTree {
|
||||||
/// A hashmap of node IDs and dynamically typed nodes.
|
/// A hashmap of node IDs and dynamically typed nodes.
|
||||||
nodes: HashMap<NodeId, (SharedNodeContainer, Path)>,
|
nodes: HashMap<NodeId, (EdgeHandle, Path)>,
|
||||||
/// A hashmap from the document path to the proto node ID.
|
/// A hashmap from the document path to the proto node ID.
|
||||||
source_map: HashMap<Path, (NodeId, NodeTypes)>,
|
source_map: HashMap<Path, (NodeId, NodeTypes)>,
|
||||||
}
|
}
|
||||||
@@ -229,44 +274,33 @@ impl BorrowTree {
|
|||||||
Ok((new_nodes, old_nodes))
|
Ok((new_nodes, old_nodes))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_deps(&self, nodes: &[NodeId]) -> Vec<SharedNodeContainer> {
|
fn node_deps(&self, nodes: &[NodeId]) -> Vec<EdgeHandle> {
|
||||||
nodes.iter().map(|node| self.nodes.get(node).unwrap().0.clone()).collect()
|
nodes.iter().map(|node| self.nodes.get(node).unwrap().0.duplicate()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_node(&mut self, node: SharedNodeContainer, id: NodeId, path: Path) {
|
fn store_node(&mut self, node: EdgeHandle, id: NodeId, path: Path) {
|
||||||
self.nodes.insert(id, (node, path));
|
self.nodes.insert(id, (node, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls the `Node::serialize` for that specific node, returning for example the cached value for a monitor node. The node path must match the document node path.
|
/// Returns the introspection record for that specific node, for example the cached value for a monitor node. The node path must match the document node path.
|
||||||
pub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
|
pub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
|
||||||
let (id, _) = self.source_map.get(node_path).ok_or_else(|| IntrospectError::PathNotFound(node_path.to_vec()))?;
|
let (id, _) = self.source_map.get(node_path).ok_or_else(|| IntrospectError::PathNotFound(node_path.to_vec()))?;
|
||||||
let (node, _path) = self.nodes.get(id).ok_or(IntrospectError::ProtoNodeNotFound(*id))?;
|
let (_node, _path) = self.nodes.get(id).ok_or(IntrospectError::ProtoNodeNotFound(*id))?;
|
||||||
node.serialize().ok_or(IntrospectError::NoData)
|
Err(IntrospectError::NoData)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: NodeId) -> Option<SharedNodeContainer> {
|
pub fn get(&self, id: NodeId) -> Option<EdgeHandle> {
|
||||||
self.nodes.get(&id).map(|(node, _)| node.clone())
|
self.nodes.get(&id).map(|(node, _)| node.duplicate())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate the output node of the [`BorrowTree`].
|
/// Evaluate a node of the [`BorrowTree`], downcasting its edge to the expected output type.
|
||||||
pub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option<O>
|
pub fn eval<I, T: 'static>(&self, id: NodeId, input: &I) -> Option<GPoll<T>>
|
||||||
where
|
where
|
||||||
I: StaticType + 'i + Send + Sync,
|
ErasedGNode<T>: GNode<I, Output = T>,
|
||||||
O: StaticType + 'i,
|
|
||||||
{
|
{
|
||||||
let (node, _path) = self.nodes.get(&id).cloned()?;
|
let (node, _path) = self.nodes.get(&id)?;
|
||||||
let output = node.eval(Box::new(input));
|
let edge = node.duplicate().downcast::<T>().ok()?;
|
||||||
dyn_any::downcast::<O>(output.await).ok().map(|o| *o)
|
Some(edge.eval(input))
|
||||||
}
|
|
||||||
/// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value.
|
|
||||||
/// This ensures that no borrowed data can escape the node graph.
|
|
||||||
pub async fn eval_tagged_value<I>(&self, id: NodeId, input: I) -> Result<TaggedValue, String>
|
|
||||||
where
|
|
||||||
I: StaticType + 'static + Send + Sync + std::panic::UnwindSafe,
|
|
||||||
{
|
|
||||||
let (node, _path) = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
|
|
||||||
let output = node.eval(Box::new(input));
|
|
||||||
TaggedValue::try_from_any(output.await)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a node from the [`BorrowTree`] and returns its associated path.
|
/// Removes a node from the [`BorrowTree`] and returns its associated path.
|
||||||
@@ -295,7 +329,7 @@ impl BorrowTree {
|
|||||||
///
|
///
|
||||||
/// async fn example() -> Result<(), GraphErrors> {
|
/// async fn example() -> Result<(), GraphErrors> {
|
||||||
/// let (proto_network, node_id, proto_node) = ProtoNetwork::example();
|
/// let (proto_network, node_id, proto_node) = ProtoNetwork::example();
|
||||||
/// let typing_context = TypingContext::new(&node_registry::NODE_REGISTRY);
|
/// let typing_context = TypingContext::default();
|
||||||
/// let mut borrow_tree = BorrowTree::new(proto_network, &typing_context).await?;
|
/// let mut borrow_tree = BorrowTree::new(proto_network, &typing_context).await?;
|
||||||
///
|
///
|
||||||
/// // Assert that the node exists in the BorrowTree
|
/// // Assert that the node exists in the BorrowTree
|
||||||
@@ -399,24 +433,17 @@ impl BorrowTree {
|
|||||||
|
|
||||||
match &proto_node.construction_args {
|
match &proto_node.construction_args {
|
||||||
ConstructionArgs::Value(value) => {
|
ConstructionArgs::Value(value) => {
|
||||||
let node = if let TaggedValue::EditorApi(api) = &**value {
|
let node = (**value)
|
||||||
let editor_api = UpcastAsRefNode::new(api.clone());
|
.clone()
|
||||||
let node = Box::new(editor_api) as TypeErasedBox<'_>;
|
.to_edge()
|
||||||
NodeContainer::new(node)
|
.map_err(|error| vec![GraphError::new(&proto_node, GraphErrorType::ConstructionFailed(error))])?;
|
||||||
} else {
|
|
||||||
let upcasted = UpcastNode::new(value.to_owned());
|
|
||||||
let node = Box::new(upcasted) as TypeErasedBox<'_>;
|
|
||||||
NodeContainer::new(node)
|
|
||||||
};
|
|
||||||
self.store_node(node, id, path.into());
|
self.store_node(node, id, path.into());
|
||||||
}
|
}
|
||||||
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
|
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
|
||||||
ConstructionArgs::Nodes(ids) => {
|
ConstructionArgs::Nodes(ids) => {
|
||||||
let ids = ids.to_vec();
|
let construction_nodes = self.node_deps(ids);
|
||||||
let construction_nodes = self.node_deps(&ids);
|
|
||||||
let constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
let constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||||
let node = constructor(construction_nodes).await;
|
let node = constructor(construction_nodes).map_err(|error| vec![GraphError::new(&proto_node, GraphErrorType::ConstructionFailed(format!("{error:?}")))])?;
|
||||||
let node = NodeContainer::new(node);
|
|
||||||
self.store_node(node, id, path.into());
|
self.store_node(node, id, path.into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -433,6 +460,59 @@ impl BorrowTree {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
use core_types::arena::ArenaCell;
|
||||||
|
use core_types::context::{ExtractFootprint, ExtractVarArgs};
|
||||||
|
use core_types::runtime::{SourceFuture, Spawner};
|
||||||
|
|
||||||
|
struct InertSpawner;
|
||||||
|
|
||||||
|
impl Spawner for InertSpawner {
|
||||||
|
fn spawn(&self, _task: SourceFuture) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_root_builds_the_bare_root_with_the_call_argument_as_vararg_0() {
|
||||||
|
let mut arena = Arena::new(64);
|
||||||
|
let runtime = GraphRuntime::new(InertSpawner);
|
||||||
|
let argument = 21.5f64;
|
||||||
|
let result = eval_root(&mut arena, &runtime, &argument, |ctx| {
|
||||||
|
assert!(ctx.try_footprint().is_none(), "the bare root carries no axes");
|
||||||
|
GPoll::Final(ctx.vararg(0).ok().and_then(|slot| slot.downcast_ref::<f64>()).copied().unwrap_or(0.))
|
||||||
|
});
|
||||||
|
assert_eq!(result, GPoll::Final(21.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_root_resets_the_arena_at_eval_start() {
|
||||||
|
let mut arena = Arena::new(64);
|
||||||
|
let runtime = GraphRuntime::new(InertSpawner);
|
||||||
|
let cell = ArenaCell::new();
|
||||||
|
eval_root(&mut arena, &runtime, &(), |ctx| {
|
||||||
|
let (_, weak) = ctx.scope().arena().alloc(5u32).unwrap();
|
||||||
|
cell.store(weak);
|
||||||
|
GPoll::Final(())
|
||||||
|
});
|
||||||
|
assert!(cell.load(&arena).is_some(), "the introspection window spans until the next eval");
|
||||||
|
eval_root(&mut arena, &runtime, &(), |ctx| {
|
||||||
|
assert!(cell.load(ctx.scope().arena()).is_none(), "the reset at eval start reclaims the previous frame");
|
||||||
|
GPoll::Final(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_panicking_eval_reports_the_error_and_resets_the_arena() {
|
||||||
|
let mut arena = Arena::new(64);
|
||||||
|
let runtime = GraphRuntime::new(InertSpawner);
|
||||||
|
let cell = ArenaCell::new();
|
||||||
|
let result: GPoll<()> = eval_root(&mut arena, &runtime, &(), |ctx| {
|
||||||
|
let (_, weak) = ctx.scope().arena().alloc(5u32).unwrap();
|
||||||
|
cell.store(weak);
|
||||||
|
panic!("mid-eval");
|
||||||
|
});
|
||||||
|
assert_eq!(result, GPoll::panicked());
|
||||||
|
assert!(cell.load(&arena).is_none(), "reset-on-panic leaves no stale records");
|
||||||
|
assert_eq!(eval_root(&mut arena, &runtime, &(), |_| GPoll::Final(7u32)), GPoll::Final(7));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn push_node_sync() {
|
fn push_node_sync() {
|
||||||
@@ -442,7 +522,12 @@ mod test {
|
|||||||
let future = tree.push_node(NodeId(0), val_1_protonode, &context);
|
let future = tree.push_node(NodeId(0), val_1_protonode, &context);
|
||||||
futures::executor::block_on(future).unwrap();
|
futures::executor::block_on(future).unwrap();
|
||||||
let _node = tree.get(NodeId(0)).unwrap();
|
let _node = tree.get(NodeId(0)).unwrap();
|
||||||
let result = futures::executor::block_on(tree.eval(NodeId(0), ()));
|
|
||||||
assert_eq!(result, Some(2u32));
|
let arena = Arena::new(64);
|
||||||
|
let generations = [];
|
||||||
|
let scope = EvalScope::new(None, None, None, &generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
let result: Option<GPoll<u32>> = tree.eval(NodeId(0), &ctx);
|
||||||
|
assert_eq!(result, Some(GPoll::Final(2)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,18 @@ impl<'i, T: Clone + 'i, I> Node<'i, I> for ClonedNode<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Clone, Input> crate::gnode::GNode<Input> for ClonedNode<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn eval(&self, _input: &Input) -> crate::gpoll::GPoll<T> {
|
||||||
|
crate::gpoll::GPoll::Final(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value_edge<T: Clone + crate::WasmNotSend + crate::WasmNotSync + 'static>(value: T) -> crate::registry::EdgeHandle {
|
||||||
|
crate::registry::EdgeHandle::new(std::sync::Arc::new(ClonedNode(value)) as std::sync::Arc<crate::registry::ErasedGNode<T>>)
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Clone> ClonedNode<T> {
|
impl<T: Clone> ClonedNode<T> {
|
||||||
pub const fn new(value: T) -> ClonedNode<T> {
|
pub const fn new(value: T) -> ClonedNode<T> {
|
||||||
ClonedNode(value)
|
ClonedNode(value)
|
||||||
|
|||||||
Reference in New Issue
Block a user