Context nullification, cached monitor nodes

This commit is contained in:
Adam 2025-07-10 01:47:40 -07:00
parent 1398405529
commit cf0a32b9b1
82 changed files with 2235 additions and 1684 deletions

View file

@ -5,8 +5,10 @@ use crate::messages::prelude::*;
#[derive(Debug, Default)]
pub struct Dispatcher {
buffered_queue: Vec<Message>,
queueing_messages: bool,
evaluation_queue: Vec<Message>,
introspection_queue: Vec<Message>,
queueing_evaluation_messages: bool,
queueing_introspection_messages: bool,
message_queues: Vec<VecDeque<Message>>,
pub responses: Vec<FrontendMessage>,
pub message_handlers: DispatcherMessageHandlers,
@ -41,6 +43,9 @@ impl DispatcherMessageHandlers {
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
/// In addition, these messages do not change any state in the backend (aside from caches).
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::CompileActiveDocument),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::EvaluateActiveDocument),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::IntrospectActiveDocument),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::Refresh,
))),
@ -91,13 +96,6 @@ impl Dispatcher {
pub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {
let message = message.into();
// Add all additional messages to the queue if it exists (except from the end queue message)
if !matches!(message, Message::EndQueue) {
if self.queueing_messages {
self.buffered_queue.push(message);
return;
}
}
// If we are not maintaining the buffer, simply add to the current queue
Self::schedule_execution(&mut self.message_queues, process_after_all_current, [message]);
@ -117,7 +115,21 @@ impl Dispatcher {
continue;
}
}
// Add all messages to the queue if queuing messages (except from the end queue message)
if !matches!(message, Message::EndEvaluationQueue) {
if self.queueing_evaluation_messages {
self.evaluation_queue.push(message);
return;
}
}
// Add all messages to the queue if queuing messages (except from the end queue message)
if !matches!(message, Message::EndIntrospectionQueue) {
if self.queueing_introspection_messages {
self.introspection_queue.push(message);
return;
}
}
// Print the message at a verbosity level of `info`
self.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
@ -126,22 +138,40 @@ impl Dispatcher {
// Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend
match message {
Message::StartQueue => {
self.queueing_messages = true;
Message::StartEvaluationQueue => {
self.queueing_evaluation_messages = true;
}
Message::EndQueue => {
self.queueing_messages = false;
Message::EndEvaluationQueue => {
self.queueing_evaluation_messages = false;
}
Message::ProcessQueue((render_output_metadata, introspected_inputs)) => {
let message = PortfolioMessage::ProcessEvaluationResponse {
Message::ProcessEvaluationQueue(render_output_metadata) => {
let update_message = PortfolioMessage::ProcessEvaluationResponse {
evaluation_metadata: render_output_metadata,
introspected_inputs,
};
// Add the message to update the state with the render output
Self::schedule_execution(&mut self.message_queues, true, [message]);
}
.into();
// Update the state with the render output and introspected inputs
Self::schedule_execution(&mut self.message_queues, true, [update_message]);
// Schedule all queued messages to be run (in the order they were added)
Self::schedule_execution(&mut self.message_queues, true, std::mem::take(&mut self.buffered_queue));
// Schedule all queued messages to be run, which use the introspected inputs (in the order they were added)
Self::schedule_execution(&mut self.message_queues, true, std::mem::take(&mut self.evaluation_queue));
}
Message::StartIntrospectionQueue => {
self.queueing_introspection_messages = true;
}
Message::EndIntrospectionQueue => {
self.queueing_introspection_messages = false;
}
Message::ProcessIntrospectionQueue(introspected_inputs) => {
let update_message = PortfolioMessage::ProcessIntrospectionResponse { introspected_inputs }.into();
// Update the state with the render output and introspected inputs
Self::schedule_execution(&mut self.message_queues, true, [update_message]);
// Schedule all queued messages to be run, which use the introspected inputs (in the order they were added)
Self::schedule_execution(&mut self.message_queues, true, std::mem::take(&mut self.introspection_queue));
let clear_message = PortfolioMessage::ClearIntrospectedData.into();
// Clear the introspected inputs since they are no longer required, and will cause a memory leak if not removed
Self::schedule_execution(&mut self.message_queues, true, [clear_message]);
}
Message::NoOp => {}
Message::Init => {

View file

@ -43,7 +43,7 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
ExportDialogMessage::TransparentBackground(transparent_background) => self.transparent_background = transparent_background,
ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area,
ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::ActiveDocumentExport {
ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::ExportActiveDocument {
file_name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
file_type: self.file_type,
scale_factor: self.scale_factor,

View file

@ -1,7 +1,7 @@
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
use glam::{IVec2, UVec2};
use graph_craft::document::NodeId;
use graphene_std::uuid::NodeId;
/// A dialog to allow users to set some initial options about a new document.
#[derive(Debug, Clone, Default, ExtractField)]
@ -24,17 +24,14 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;
if create_artboard {
responses.add(Message::StartQueue);
responses.add(GraphOperationMessage::NewArtboard {
id: NodeId::new(),
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
});
}
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
responses.add(Message::StartQueue);
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
responses.add(Message::StartEvaluationQueue);
responses.add(DocumentMessage::ZoomCanvasToFitAll);
responses.add(Message::EndEvaluationQueue);
responses.add(DocumentMessage::DeselectAllLayers);
}
}

View file

@ -7,7 +7,7 @@ use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, La
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData;
use graph_craft::document::NodeId;
use graphene_std::uuid::NodeId;
use graphene_std::raster::color::Color;
use graphene_std::text::Font;

View file

@ -3,8 +3,7 @@ use crate::messages::input_mapper::utility_types::input_mouse::{MouseButton, Mou
use crate::messages::input_mapper::utility_types::misc::FrameTimeInfo;
use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
use crate::messages::prelude::*;
use glam::DVec2;
use std::time::Duration;
use glam::{DAffine2, DVec2};
#[derive(ExtractField)]
pub struct InputPreprocessorMessageContext {

View file

@ -1,27 +1,24 @@
use std::sync::Arc;
use crate::messages::prelude::*;
use graphene_std::{IntrospectMode, uuid::CompiledProtonodeInput};
use crate::{messages::prelude::*, node_graph_executor::IntrospectionResponse};
use graphite_proc_macros::*;
#[impl_message]
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Message {
NoOp,
Init,
Batched(Box<[Message]>),
// Adds any subsequent messages to the queue
StartQueue,
StartEvaluationQueue,
// Stop adding messages to the queue.
EndQueue,
// Processes all messages that are queued, which occurs on the evaluation response. This allows a message to be run with data from after the evaluation is complete
ProcessQueue(
(
graphene_std::renderer::RenderMetadata,
Vec<(CompiledProtonodeInput, IntrospectMode, Box<dyn std::any::Any + Send + Sync>)>,
),
),
EndEvaluationQueue,
// Processes all messages that are queued to be run after evaluation, which occurs on the evaluation response. This allows a message to be run with data from after the evaluation is complete
#[serde(skip)]
ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata),
StartIntrospectionQueue,
EndIntrospectionQueue,
// Processes all messages that are queued to be run after introspection, which occurs on the evaluation response. This allows a message to be run with data from after the evaluation is complete
#[serde(skip)]
ProcessIntrospectionQueue(IntrospectionResponse),
#[child]
Animation(AnimationMessage),
#[child]

View file

@ -7,11 +7,10 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
use crate::messages::portfolio::utility_types::PanelType;
use crate::messages::prelude::*;
use glam::DAffine2;
use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::vector::click_target::ClickTarget;
use graphene_std::Color;
use graphene_std::raster::BlendMode;
use graphene_std::raster::Image;
use graphene_std::renderer::ClickTarget;
use graphene_std::transform::Footprint;
use graphene_std::uuid::NodeId;
use graphene_std::vector::style::ViewMode;

View file

@ -3,7 +3,7 @@ use super::node_graph::utility_types::Transform;
use super::overlays::utility_types::Pivot;
use super::utility_types::error::EditorError;
use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState};
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
use super::utility_types::network_interface::{NodeNetworkInterface, TransactionStatus};
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid};
use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
@ -13,10 +13,9 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf
use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings};
use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeTemplate};
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
@ -24,11 +23,10 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self
use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
use crate::messages::tool::tool_messages::tool_prelude::Key;
use crate::messages::tool::utility_types::ToolType;
use crate::node_graph_executor::NodeGraphExecutor;
use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
use graph_craft::document::{InputConnector, NodeInput, NodeNetwork, OldNodeNetwork};
use graphene_std::math::quad::Quad;
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
use graphene_std::raster::BlendMode;
@ -37,18 +35,16 @@ use graphene_std::uuid::NodeId;
use graphene_std::vector::PointId;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::style::ViewMode;
use std::sync::Arc;
use std::time::Duration;
#[derive(ExtractField)]
pub struct DocumentMessageContext<'a> {
pub document_id: DocumentId,
pub struct DocumentMessageData<'a> {
pub ipp: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
pub current_tool: &'a ToolType,
pub preferences: &'a PreferencesMessageHandler,
pub device_pixel_ratio: f64,
// pub introspected_inputs: &HashMap<CompiledProtonodeInput, Box<dyn std::any::Any + Send + Sync>>,
// pub introspected_inputs: &HashMap<CompiledProtonodeInput, Arc<dyn std::any::Any + Send + Sync>>,
// pub downcasted_inputs: &mut HashMap<CompiledProtonodeInput, TaggedValue>,
}
@ -173,10 +169,9 @@ impl Default for DocumentMessageHandler {
}
#[message_handler_data]
impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, context: DocumentMessageContext) {
let DocumentMessageContext {
document_id,
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) {
let DocumentMessageData {
ipp,
persistent_data,
current_tool,
@ -222,12 +217,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
);
}
DocumentMessage::PropertiesPanel(message) => {
let context = PropertiesPanelMessageContext {
let properties_panel_message_handler_data = super::properties_panel::PropertiesPanelMessageHandlerData {
network_interface: &mut self.network_interface,
selection_network_path: &self.selection_network_path,
document_name: self.name.as_str(),
executor,
persistent_data,
};
self.properties_panel_message_handler.process_message(message, responses, context);
}
@ -239,7 +232,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
network_interface: &mut self.network_interface,
selection_network_path: &self.selection_network_path,
breadcrumb_network_path: &self.breadcrumb_network_path,
document_id,
collapsed: &mut self.collapsed,
ipp,
graph_view_overlay_open: self.graph_view_overlay_open,
@ -1432,7 +1424,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
center: Key::Alt,
duplicate: Key::Alt,
}));
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(PortfolioMessage::EvaluateActiveDocument);
} else {
let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {
return;
@ -1492,7 +1484,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
// Connect the current output data to the artboard's input data, and the artboard's output to the document output
responses.add(NodeGraphMessage::InsertNodeBetween {
node_id,
input_connector: network_interface::InputConnector::Export(0),
input_connector: InputConnector::Export(0),
insert_node_input_index: 1,
});
@ -1914,13 +1906,14 @@ impl DocumentMessageHandler {
let previous_network = std::mem::replace(&mut self.network_interface, network_interface);
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartEvaluationQueue);
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartQueue);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires);
responses.add(Message::EndEvaluationQueue);
Some(previous_network)
}
pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
@ -1946,12 +1939,14 @@ impl DocumentMessageHandler {
network_interface.set_document_to_viewport_transform(transform);
let previous_network = std::mem::replace(&mut self.network_interface, network_interface);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartEvaluationQueue);
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires);
responses.add(Message::EndEvaluationQueue);
Some(previous_network)
}
@ -2108,7 +2103,7 @@ impl DocumentMessageHandler {
/// Loads all of the fonts in the document.
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
let mut fonts = HashSet::new();
for (_node_id, node, _) in self.document_network().recursive_nodes() {
for (_node_path, node) in self.document_network().recursive_nodes() {
for input in &node.inputs {
if let Some(TaggedValue::Font(font)) = input.as_value() {
fonts.insert(font.clone());
@ -2581,7 +2576,7 @@ impl DocumentMessageHandler {
layout: Layout::WidgetLayout(document_bar_layout),
layout_target: LayoutTarget::DocumentBar,
});
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
responses.add(PortfolioMessage::EvaluateActiveDocument);
}
pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque<Message>) {

View file

@ -4,12 +4,12 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
use crate::messages::prelude::*;
use bezier_rs::Subpath;
use glam::{DAffine2, IVec2};
use graph_craft::document::NodeId;
use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, RasterDataTable};
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::uuid::NodeId;
use graphene_std::vector::PointId;
use graphene_std::vector::VectorModificationType;
use graphene_std::vector::style::{Fill, Stroke};

View file

@ -2,12 +2,13 @@ use super::transform_utils;
use super::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface, OutputConnector};
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::document::{InputConnector, OutputConnector, NodeInput};
use graphene_std::uuid::NodeId;
use graphene_std::Color;
use graphene_std::renderer::Quad;
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;

View file

@ -1,8 +1,9 @@
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use bezier_rs::Subpath;
use glam::{DAffine2, DVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::document::{InputConnector, NodeInput};
use graphene_std::uuid::NodeId;
use graphene_std::vector::PointId;
/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.

View file

@ -1,18 +1,19 @@
use super::transform_utils;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
use crate::messages::portfolio::document::utility_types::network_interface::{self, NodeNetworkInterface};
use crate::messages::prelude::*;
use bezier_rs::Subpath;
use glam::{DAffine2, IVec2};
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::document::{InputConnector, NodeInput, OutputConnector};
use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, RasterDataTable};
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::uuid::NodeId;
use graphene_std::vector::style::{Fill, Stroke};
use graphene_std::vector::{PointId, VectorModificationType};
use graphene_std::vector::{VectorData, VectorDataTable};

View file

@ -11,7 +11,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId;
use graphene_std::uuid::NodeId;
#[derive(ExtractField)]
pub struct NavigationMessageContext<'a> {

View file

@ -10,7 +10,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
};
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::Message;
use crate::node_graph_executor::NodeGraphExecutor;
use glam::DVec2;
use graph_craft::ProtoNodeIdentifier;
use graph_craft::concrete;
@ -23,6 +22,7 @@ use graphene_std::raster_types::{CPU, RasterDataTable};
use graphene_std::text::{Font, TypesettingConfig};
#[allow(unused_imports)]
use graphene_std::transform::Footprint;
use graphene_std::uuid::NodeId;
use graphene_std::vector::VectorDataTable;
use graphene_std::*;
use std::collections::{HashMap, HashSet, VecDeque};

View file

@ -1,13 +1,12 @@
use super::utility_types::Direction;
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, InputConnector, NodeTemplate, OutputConnector};
use crate::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, NodeTemplate};
use crate::messages::prelude::*;
use glam::IVec2;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::proto::GraphErrors;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
use graph_craft::document::{InputConnector, NodeInput, OutputConnector};
use graphene_std::uuid::NodeId;
#[impl_message(Message, DocumentMessage, NodeGraph)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
@ -111,8 +110,6 @@ pub enum NodeGraphMessage {
start_index: usize,
end_index: usize,
},
RunDocumentGraph,
ForceRunDocumentGraph,
SelectedNodesAdd {
nodes: Vec<NodeId>,
},

View file

@ -9,9 +9,7 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
use crate::messages::portfolio::document::utility_types::network_interface::{
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
};
use crate::messages::portfolio::document::utility_types::network_interface::{self, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, Previewing, TypeSource};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
use crate::messages::prelude::*;
@ -20,9 +18,10 @@ use crate::messages::tool::common_functionality::graph_modification_utils::get_c
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
use graph_craft::document::{DocumentNodeImplementation, InputConnector, NodeInput, OutputConnector};
use graph_craft::proto::GraphErrors;
use graphene_std::math::math_ext::QuadExt;
use graphene_std::uuid::NodeId;
use graphene_std::*;
use renderer::Quad;
use std::cmp::Ordering;
@ -32,7 +31,6 @@ pub struct NodeGraphMessageContext<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],
pub breadcrumb_network_path: &'a [NodeId],
pub document_id: DocumentId,
pub collapsed: &'a mut CollapsedLayers,
pub ipp: &'a InputPreprocessorMessageHandler,
pub graph_view_overlay_open: bool,
@ -99,7 +97,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
network_interface,
selection_network_path,
breadcrumb_network_path,
document_id,
collapsed,
ipp,
graph_view_overlay_open,
@ -767,14 +764,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
self.initial_disconnecting = false;
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
if let Some((output_type, source)) = clicked_output
.node_id()
.map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path))
{
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source);
} else {
self.wire_in_progress_type = FrontendGraphDataType::General;
}
let (output_type, source) = network_interface.output_type(&clicked_output, breadcrumb_network_path);
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source);
self.update_node_graph_hints(responses);
return;
@ -922,7 +913,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
false
}
});
let vector_wire = build_vector_wire(
let (vector_wire, _) = build_vector_wire(
wire_in_progress_from_connector,
wire_in_progress_to_connector,
from_connector_is_layer,
@ -936,6 +927,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
data_type: self.wire_in_progress_type,
thick: false,
dashed: false,
center: None,
input_sni: None,
};
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) });
}
@ -1078,15 +1071,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
};
// Get the compatible type from the output connector
let compatible_type = output_connector.and_then(|output_connector| {
output_connector.node_id().and_then(|node_id| {
// Get the output types from the network interface
let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path);
// Get the output types from the network interface
let (output_type, type_source) = network_interface.output_type(&output_connector, selection_network_path);
match type_source {
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
_ => Some(format!("type:{}", output_type.nested_type())),
}
})
match type_source {
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
_ => Some(format!("type:{}", output_type.nested_type())),
}
});
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
let appear_above_mouse = if ipp.mouse.position.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. };
@ -1188,7 +1179,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
{
return None;
}
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
let (wire, is_stack, _) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
})
.collect::<Vec<_>>();
@ -1290,12 +1281,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(NodeGraphMessage::SendGraph);
responses.add(PortfolioMessage::CompileActiveDocument);
}
PortfolioMessage::CompileActiveDocument => {
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
}
NodeGraphMessage::ForceRunDocumentGraph => {
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: true });
}
NodeGraphMessage::SelectedNodesAdd { nodes } => {
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
log::error!("Could not get selected nodes in NodeGraphMessage::SelectedNodesAdd");
@ -1340,14 +1325,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
return;
};
let document_bbox: [DVec2; 2] = ipp.document_bounds();
let document_bbox = ipp.document_bounds(network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse());
let mut nodes = Vec::new();
for node_id in &self.frontend_nodes {
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
log::error!("Could not get bbox for node: {:?}", node_id);
continue;
};
if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y {
nodes.push(*node_id);
}
@ -1393,6 +1377,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(PropertiesPanelMessage::Refresh);
if !(network_interface.reference(&node_id, selection_network_path).is_none() || input_index == 0) && network_interface.connected_to_output(&node_id, selection_network_path) {
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartEvaluationQueue);
responses.add(NodeGraphMessage::SendWires);
responses.add(Message::EndEvaluationQueue);
}
}
NodeGraphMessage::SetInput { input_connector, input } => {
@ -1688,8 +1675,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
)
.into_iter()
.next();
responses.add(NodeGraphMessage::UpdateVisibleNodes);
responses.add(NodeGraphMessage::SendWires);
responses.add(FrontendMessage::UpdateImportsExports {
imports,
exports,
@ -2182,6 +2167,7 @@ impl NodeGraphMessageHandler {
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
let Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path).cloned() else {
log::error!("Could not collect outward wires in collect_nodes");
return Vec::new();
};
let mut can_be_layer_lookup = HashSet::new();
@ -2232,7 +2218,7 @@ impl NodeGraphMessageHandler {
let primary_input = inputs.next().flatten();
let exposed_inputs = inputs.flatten().collect();
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
let (output_type, type_source) = network_interface.output_type(&OutputConnector::node(node_id, 0), breadcrumb_network_path);
let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default();
@ -2253,7 +2239,7 @@ impl NodeGraphMessageHandler {
if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
continue;
}
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
let (output_type, type_source) = network_interface.output_type(&OutputConnector::node(node_id, 0), breadcrumb_network_path);
let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else {
@ -2292,18 +2278,19 @@ impl NodeGraphMessageHandler {
let locked = network_interface.is_locked(&node_id, breadcrumb_network_path);
let errors = self
.node_graph_errors
.iter()
.find(|error| error.node_path == node_id_path)
.map(|error| format!("{:?}", error.error.clone()))
.or_else(|| {
if self.node_graph_errors.iter().any(|error| error.node_path.starts_with(&node_id_path)) {
Some("Node graph type error within this node".to_string())
} else {
None
}
});
let errors = None; // TODO: Recursive traversal from export over all protonodes and match metadata with error
// self
// .node_graph_errors
// .iter()
// .find(|error| error.stable_node_id == node_id_path)
// .map(|error| format!("{:?}", error.error.clone()))
// .or_else(|| {
// if self.node_graph_errors.iter().any(|error| error.node_path.starts_with(&node_id_path)) {
// Some("Node graph type error within this node".to_string())
// } else {
// None
// }
// });
nodes.push(FrontendNode {
id: node_id,

View file

@ -3,14 +3,13 @@
use super::document_node_definitions::{NODE_OVERRIDES, NodePropertiesContext};
use super::utility_types::FrontendGraphDataType;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
use crate::messages::prelude::*;
use choice::enum_choice;
use dyn_any::DynAny;
use glam::{DAffine2, DVec2, IVec2, UVec2};
use graph_craft::Type;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, InputConnector, NodeInput};
use graphene_std::animation::RealTimeMode;
use graphene_std::extract_xy::XY;
use graphene_std::path_bool::BooleanOperation;
@ -22,6 +21,7 @@ use graphene_std::raster::{
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
use graphene_std::text::Font;
use graphene_std::transform::{Footprint, ReferencePoint};
use graphene_std::uuid::NodeId;
use graphene_std::vector::VectorDataTable;
use graphene_std::vector::misc::GridType;
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};

View file

@ -1,6 +1,7 @@
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector, TypeSource};
use graph_craft::document::NodeId;
use crate::messages::portfolio::document::utility_types::network_interface::{TypeSource};
use graph_craft::document::{InputConnector, OutputConnector};
use graph_craft::document::value::TaggedValue;
use graphene_std::uuid::NodeId;
use graphene_std::Type;
use std::borrow::Cow;
@ -72,7 +73,7 @@ pub struct FrontendGraphOutput {
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNode {
pub id: graph_craft::document::NodeId,
pub id: NodeId,
#[serde(rename = "isLayer")]
pub is_layer: bool,
#[serde(rename = "canBeLayer")]

View file

@ -1,7 +1,7 @@
mod properties_panel_message;
pub mod properties_panel_message_handler;
mod properties_panel_message_handler;
#[doc(inline)]
pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant};
#[doc(inline)]
pub use properties_panel_message_handler::PropertiesPanelMessageHandler;
pub use properties_panel_message_handler::{PropertiesPanelMessageHandler, PropertiesPanelMessageHandlerData};

View file

@ -7,7 +7,6 @@ use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use graph_craft::document::NodeId;
pub struct PropertiesPanelMessageHandlerData<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],

View file

@ -1,5 +1,6 @@
use graphene_std::uuid::NodeId;
use super::network_interface::NodeTemplate;
use graph_craft::document::NodeId;
#[repr(u8)]
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]

View file

@ -4,9 +4,9 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Modify
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId;
use graphene_std::math::quad::Quad;
use graphene_std::transform::Footprint;
use graphene_std::uuid::NodeId;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::{PointId, VectorData};
use std::collections::{HashMap, HashSet};

View file

@ -11,15 +11,13 @@ use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode;
use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, InputConnector, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork, OutputConnector};
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, InputConnector, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork, OutputConnector};
use graph_craft::{Type, concrete};
use graphene_std::math::quad::Quad;
use graphene_std::transform::Footprint;
use graphene_std::uuid::{CompiledProtonodeInput, NodeId};
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI};
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::{PointId, VectorData, VectorModificationType};
use graphene_std::{CompiledProtonodeInput, SNI};
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
use interpreted_executor::node_registry::NODE_REGISTRY;
use serde_json::{Value, json};
use std::collections::{HashMap, HashSet, VecDeque};
@ -38,8 +36,9 @@ pub struct NodeNetworkInterface {
#[serde(skip)]
document_metadata: DocumentMetadata,
/// All input/output types based on the compiled network.
/// TODO: Move to portfolio message handler
#[serde(skip)]
pub resolved_types: ResolvedDocumentNodeTypes,
pub resolved_types: HashMap<SNI, Vec<Type>>,
#[serde(skip)]
transaction_status: TransactionStatus,
#[serde(skip)]
@ -54,6 +53,7 @@ impl Clone for NodeNetworkInterface {
document_metadata: Default::default(),
resolved_types: Default::default(),
transaction_status: TransactionStatus::Finished,
current_hash: 0,
}
}
}
@ -490,7 +490,7 @@ impl NodeNetworkInterface {
}
/// Try and get the [`DocumentNodeDefinition`] for a node
pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> {
pub fn node_definition(&self, node_id: NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeDefinition> {
let metadata = self.node_metadata(&node_id, network_path)?;
resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?)
}
@ -512,13 +512,13 @@ impl NodeNetworkInterface {
}
}
pub fn downstream_caller_from_output(&self, output_connector: OutputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> {
pub fn downstream_caller_from_output(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> {
match output_connector {
OutputConnector::Node { node_id, output_index } => match self.implementation(&node_id, network_path)? {
DocumentNodeImplementation::Network(node_network) => {
OutputConnector::Node { node_id, output_index } => match self.implementation(node_id, network_path)? {
DocumentNodeImplementation::Network(_) => {
let mut nested_path = network_path.to_vec();
nested_path.push(node_id);
self.downstream_caller_from_input(InputConnector::Export(output_index), &nested_path)
nested_path.push(*node_id);
self.downstream_caller_from_input(&InputConnector::Export(*output_index), &nested_path)
}
DocumentNodeImplementation::ProtoNode(_) => self.node_metadata(&node_id, network_path)?.transient_metadata.caller.as_ref(),
DocumentNodeImplementation::Extract => todo!(),
@ -526,44 +526,44 @@ impl NodeNetworkInterface {
OutputConnector::Import(import_index) => {
let mut encapsulating_path = network_path.to_vec();
let node_id = encapsulating_path.pop().expect("No imports in document network");
self.downstream_caller_from_input(InputConnector::node(node_id, import_index), &encapsulating_path)
self.downstream_caller_from_input(&InputConnector::node(node_id, *import_index), &encapsulating_path)
}
}
}
// Returns the path and input index to the protonode which called the input, which has to be the same every time is is called for a given input.
// This has to be done by iterating upstream, since a downstream traversal may lead to an uncompiled branch.
// This requires that value inputs store their caller. Caller input metadata from compilation has to be stored for
pub fn downstream_caller_from_input(&self, &input_connector: InputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> {
pub fn downstream_caller_from_input(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&CompiledProtonodeInput> {
// Cases: Node/Value input to protonode, Node/Value input to network node
let input = self.input_from_connector(input_connector, network_path)?;
let caller_input = match input {
NodeInput::Node { node_id, output_index, lambda } => {
NodeInput::Node { node_id, output_index, .. } => {
match self.implementation(node_id, network_path)? {
DocumentNodeImplementation::Network(node_network) => {
DocumentNodeImplementation::Network(_) => {
// Continue traversal within network
let mut nested_path = network_path.to_vec();
nested_path.push(*node_id);
self.downstream_caller_from_input(InputConnector::Export(*output_index), &nested_path)
self.downstream_caller_from_input(&InputConnector::Export(*output_index), &nested_path)
}
DocumentNodeImplementation::ProtoNode(proto_node_identifier) => self.node_metadata(node_id, network_path)?.transient_metadata.caller.as_ref(),
DocumentNodeImplementation::ProtoNode(_) => self.node_metadata(node_id, network_path)?.transient_metadata.caller.as_ref(),
// If connected to a protonode, use the data in the node metadata
DocumentNodeImplementation::Extract => todo!(),
}
}
// Can either be an input to a protonode, network node, or export
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Reflection(_) => match input_connector {
InputConnector::Node { node_id, input_index } => self.input_metadata(node_id, *index, network_path)?.transient_metadata.caller.as_ref(),
InputConnector::Export(export_index) => self.network_metadata(network_path)?.transient_metadata.callers.get(export_index)?.as_ref(),
InputConnector::Node { node_id, .. } => self.transient_input_metadata(node_id, input_connector.input_index(), network_path)?.caller.as_ref(),
InputConnector::Export(export_index) => self.network_metadata(network_path)?.transient_metadata.callers.get(*export_index)?.as_ref(),
},
NodeInput::Network { import_index } => {
NodeInput::Network { import_index, .. } => {
let mut encapsulating_path = network_path.to_vec();
let node_id = encapsulating_path.pop().expect("No imports in document network");
self.downstream_caller_from_input(InputConnector::node(node_id, *import_index), &encapsulating_path)
self.downstream_caller_from_input(&InputConnector::node(node_id, *import_index), &encapsulating_path)
}
NodeInput::Inline(inline_rust) => None,
NodeInput::Inline(_) => None,
};
let Some(caller_input) = caller_input else {
log::error!("Could not get compiled caller input for input: {:?}", input_connector);
log::error!("Could not get compiled caller input for input: {:?} in network: {:?}", input_connector, network_path);
return None;
};
Some(caller_input)
@ -631,7 +631,7 @@ impl NodeNetworkInterface {
{
let mut inner_path = network_path.to_vec();
inner_path.push(node_id);
let result = self.guess_type_from_node(child_id, child_input_index, inner_path);
let result = self.guess_type_from_node(child_id, child_input_index, &inner_path);
inner_path.pop();
return result;
}
@ -645,6 +645,10 @@ impl NodeNetworkInterface {
/// Get the [`Type`] for any InputConnector
pub fn input_type(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> (Type, TypeSource) {
if let Some(NodeInput::Value { tagged_value, .. }) = self.input_from_connector(input_connector, network_path) {
return (tagged_value.ty(), TypeSource::TaggedValue);
}
if let Some(compiled_type) = self
.downstream_caller_from_input(input_connector, network_path)
.and_then(|(sni, input_index)| self.resolved_types.get(sni).and_then(|protonode_input_types| protonode_input_types.get(*input_index)))
@ -657,17 +661,14 @@ impl NodeNetworkInterface {
return (concrete!(()), TypeSource::Error("input connector is not a node"));
};
self.guess_type_from_node(node_id, input_connector.input_index(), network_path);
self.guess_type_from_node(node_id, input_connector.input_index(), network_path)
}
pub fn compiled_output_type(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<&Type> {
let (sni, input_index) = self.downstream_caller_from_output(output_connector, network_path)?;
let protonode_input_types = self.resolved_types.get(sni)?;
protonode_input_types.get(*input_index)
}
pub fn output_type(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> (Type, TypeSource) {
if let Some(output_type) = self.compiled_output_type(output_connector, network_path) {
pub fn output_type(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> (Type, TypeSource) {
if let Some(output_type) = self
.downstream_caller_from_output(output_connector, network_path)
.and_then(|(sni, input_index)| self.resolved_types.get(sni).and_then(|protonode_input_types| protonode_input_types.get(*input_index)))
{
return (output_type.clone(), TypeSource::Compiled);
}
(concrete!(()), TypeSource::Error("Not compiled"))
@ -678,10 +679,10 @@ impl NodeNetworkInterface {
}
pub fn remove_type(&mut self, sni: SNI) {
self.resolved_types.remove(sni);
self.resolved_types.remove(&sni);
}
pub fn set_node_caller(&mut self, node: &NodeId, caller: CompiledProtonodeInput, network_path: &[NodeId]) {
pub fn set_node_caller(&mut self, node_id: &NodeId, caller: CompiledProtonodeInput, network_path: &[NodeId]) {
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else {
return;
};
@ -692,6 +693,7 @@ impl NodeNetworkInterface {
match input_connector {
InputConnector::Node { node_id, input_index } => {
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else {
log::error!("node metadata must exist when setting input caller for node {}, input index {}", node_id, input_index);
return;
};
let Some(input_metadata) = metadata.persistent_metadata.input_metadata.get_mut(*input_index) else {
@ -770,84 +772,6 @@ impl NodeNetworkInterface {
}
}
/// Retrieves the output types for a given document node and its exports.
///
/// This function traverses the node and its nested network structure (if applicable) to determine
/// the types of all outputs, including the primary output and any additional exports.
///
/// # Arguments
///
/// * `node` - A reference to the `DocumentNode` for which to determine output types.
/// * `resolved_types` - A reference to `ResolvedDocumentNodeTypes` containing pre-resolved type information.
/// * `node_id_path` - A slice of `NodeId`s representing the path to the current node in the document graph.
///
/// # Returns
///
/// A `Vec<Option<Type>>` where:
/// - The first element is the primary output type of the node.
/// - Subsequent elements are types of additional exports (if the node is a network).
/// - `None` values indicate that a type couldn't be resolved for a particular output.
///
/// # Behavior
///
/// 1. Retrieves the primary output type from `resolved_types`.
/// 2. If the node is a network:
/// - Iterates through its exports (skipping the first/primary export).
/// - For each export, traverses the network until reaching a protonode or terminal condition.
/// - Determines the output type based on the final node/value encountered.
/// 3. Collects and returns all resolved types.
///
/// # Note
///
/// This function assumes that export indices and node IDs always exist within their respective
/// collections. It will panic if these assumptions are violated.
///
pub fn output_type(&self, node_id: &NodeId, output_index: usize, network_path: &[NodeId]) -> (Type, TypeSource) {
let Some(implementation) = self.implementation(node_id, network_path) else {
log::error!("Could not get output type for node {node_id} output index {output_index}. This node is no longer supported, and needs to be upgraded.");
return (concrete!(()), TypeSource::Error("Could not get implementation"));
};
// If the node is not a protonode, get types by traversing across exports until a proto node is reached.
match &implementation {
graph_craft::document::DocumentNodeImplementation::Network(internal_network) => {
let Some(export) = internal_network.exports.get(output_index) else {
return (concrete!(()), TypeSource::Error("Could not get export index"));
};
match export {
NodeInput::Node {
node_id: nested_node_id,
output_index,
..
} => self.output_type(nested_node_id, *output_index, &[network_path, &[*node_id]].concat()),
NodeInput::Value { tagged_value, .. } => (tagged_value.ty(), TypeSource::TaggedValue),
NodeInput::Network { .. } => {
// let mut encapsulating_path = network_path.to_vec();
// let encapsulating_node = encapsulating_path.pop().expect("No imports exist in document network");
// self.input_type(&InputConnector::node(encapsulating_node, *import_index), network_path)
(concrete!(()), TypeSource::Error("Could not type from network"))
}
NodeInput::Scope(_) => todo!(),
NodeInput::Inline(_) => todo!(),
NodeInput::Reflection(_) => todo!(),
}
}
graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode) => {
let node_id_path = &[network_path, &[*node_id]].concat();
self.resolved_types
.types
.get(node_id_path)
.map(|ty| (ty.output.clone(), TypeSource::Compiled))
.or_else(|| {
let node_types = random_protonode_implementation(protonode)?;
Some((node_types.return_value.clone(), TypeSource::RandomProtonodeImplementation))
})
.unwrap_or((concrete!(()), TypeSource::Error("Could not get protonode implementation")))
}
graph_craft::document::DocumentNodeImplementation::Extract => (concrete!(()), TypeSource::Error("extract node")),
}
}
pub fn position(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<IVec2> {
let top_left_position = self
.node_click_targets(node_id, network_path)
@ -1231,7 +1155,7 @@ impl NodeNetworkInterface {
/// Returns the description of the node, or an empty string if it is not set.
pub fn description(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {
self.get_node_definition(network_path, *node_id)
self.node_definition(*node_id, network_path)
.map(|node_definition| node_definition.description.to_string())
.filter(|description| description != "TODO")
.unwrap_or_default()
@ -1568,7 +1492,7 @@ impl NodeNetworkInterface {
continue;
};
nested_network.exports = old_network.exports;
nested_network.scope_injections = old_network.scope_injections.into_iter().collect();
// nested_network.scope_injections = old_network.scope_injections.into_iter().collect();
let Some(nested_network_metadata) = network_metadata.nested_metadata_mut(&network_path) else {
log::error!("Could not get nested network in from_old_network");
continue;
@ -1621,6 +1545,7 @@ impl NodeNetworkInterface {
document_metadata: DocumentMetadata::default(),
resolved_types: HashMap::new(),
transaction_status: TransactionStatus::Finished,
current_hash: 0,
}
}
}
@ -2789,7 +2714,7 @@ impl NodeNetworkInterface {
let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0);
let vertical_start: bool = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path));
let thick = vertical_end && vertical_start;
let vector_wire = build_vector_wire(output_position, input_position, vertical_start, vertical_end, graph_wire_style);
let (vector_wire, _) = build_vector_wire(output_position, input_position, vertical_start, vertical_end, graph_wire_style);
let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
@ -2799,6 +2724,8 @@ impl NodeNetworkInterface {
data_type,
thick,
dashed: false,
center: None,
input_sni: None,
});
Some(WirePathUpdate {
@ -2809,14 +2736,14 @@ impl NodeNetworkInterface {
}
/// Returns the vector subpath and a boolean of whether the wire should be thick.
pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(Subpath<PointId>, bool)> {
pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(Subpath<PointId>, bool, DVec2)> {
let Some(input_position) = self.get_input_center(input, network_path) else {
log::error!("Could not get dom rect for wire end: {:?}", input);
return None;
};
// An upstream output could not be found, so the wire does not exist, but it should still be loaded as as empty vector
let Some(upstream_output) = self.upstream_output_connector(input, network_path) else {
return Some((Subpath::from_anchors(std::iter::empty(), false), false));
return Some((Subpath::from_anchors(std::iter::empty(), false), false, DVec2::default()));
};
let Some(output_position) = self.get_output_center(&upstream_output, network_path) else {
log::error!("Could not get dom rect for wire start: {:?}", upstream_output);
@ -2825,19 +2752,23 @@ impl NodeNetworkInterface {
let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0);
let vertical_start = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path));
let thick = vertical_end && vertical_start;
Some((build_vector_wire(output_position, input_position, vertical_start, vertical_end, wire_style), thick))
let (wire, center) = build_vector_wire(output_position, input_position, vertical_start, vertical_end, wire_style);
Some((wire, thick, center))
}
pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option<WirePath> {
let (vector_wire, thick) = self.vector_wire_from_input(input, graph_wire_style, network_path)?;
let (vector_wire, thick, center) = self.vector_wire_from_input(input, graph_wire_style, network_path)?;
let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
let data_type = FrontendGraphDataType::from_type(&self.input_type(input, network_path).0);
let input_sni = self.downstream_caller_from_input(input, network_path).map(|caller| NodeId(caller.0.0 + caller.1 as u64));
Some(WirePath {
path_string,
data_type,
thick,
dashed,
center: Some((center.x, center.y)),
input_sni,
})
}
@ -6562,7 +6493,6 @@ impl InputPersistentMetadata {
struct InputTransientMetadata {
wire: TransientMetadata<WirePathUpdate>,
caller: Option<CompiledProtonodeInput>,
input_type: Option<Type>,
}
// TODO: Eventually remove this migration document upgrade code

View file

@ -12,6 +12,9 @@ pub struct WirePath {
pub data_type: FrontendGraphDataType,
pub thick: bool,
pub dashed: bool,
pub center: Option<(f64, f64)>,
#[serde(rename = "inputSni")]
pub input_sni: Option<NodeId>,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@ -53,7 +56,7 @@ impl GraphWireStyle {
}
}
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath<PointId> {
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> (Subpath<PointId>, DVec2) {
let grid_spacing = 24.;
match graph_wire_style {
GraphWireStyle::Direct => {
@ -85,7 +88,7 @@ pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
Subpath::new(
let subpath = Subpath::new(
vec![
ManipulatorGroup {
anchor: locations[0],
@ -113,7 +116,9 @@ pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical
},
],
false,
)
);
let center = subpath.center().unwrap();
(subpath, center)
}
GraphWireStyle::GridAligned => {
let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in);
@ -446,13 +451,13 @@ fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_o
vec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)]
}
fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
fn straight_wire_subpath(locations: Vec<IVec2>) -> (Subpath<PointId>, DVec2) {
if locations.is_empty() {
return Subpath::new(Vec::new(), false);
return (Subpath::new(Vec::new(), false), DVec2::default());
}
if locations.len() == 2 {
return Subpath::new(
let subpath = Subpath::new(
vec![
ManipulatorGroup {
anchor: locations[0].into(),
@ -469,6 +474,8 @@ fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
],
false,
);
let center = subpath.center().unwrap();
return (subpath, center);
}
let corner_radius = 10;
@ -585,5 +592,7 @@ fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
out_handle: None,
id: PointId::generate(),
});
Subpath::new(path, false)
let subpath = Subpath::new(path, false);
let center = subpath.center().unwrap();
(subpath, center)
}

View file

@ -3,12 +3,13 @@
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use crate::messages::prelude::DocumentMessageHandler;
use bezier_rs::Subpath;
use glam::IVec2;
use graph_craft::document::DocumentNode;
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
use graph_craft::document::{InputConnector, OutputConnector};
use graphene_std::ProtoNodeIdentifier;
use graphene_std::text::TypesettingConfig;
use graphene_std::uuid::NodeId;
@ -492,7 +493,8 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
}
});
for (node_id, node, network_path) in network.recursive_nodes() {
for (node_path, node) in network.recursive_nodes() {
let (node_id, network_path) = node_path.split_last().unwrap();
if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation {
let node_path_without_type_args = protonode_id.name.split('<').next();
if let Some(new) = node_path_without_type_args.and_then(|node_path| replacements.get(node_path)) {
@ -509,9 +511,9 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
.network_interface
.document_network()
.recursive_nodes()
.map(|(node_id, node, path)| (*node_id, node.clone(), path))
.collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>();
for (node_id, node, network_path) in &nodes {
.map(|(node_path, node)| (node_path, node.clone()))
.collect::<Vec<(Vec<NodeId>, graph_craft::document::DocumentNode)>>();
for (node_path, node) in &nodes {
migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open);
}
}
@ -523,7 +525,6 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());
}
}
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) {
document

View file

@ -1,17 +1,15 @@
use std::sync::Arc;
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
use super::utility_types::PanelType;
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::prelude::*;
use crate::node_graph_executor::CompilationResponse;
use crate::node_graph_executor::IntrospectionResponse;
use graph_craft::document::CompilationMetadata;
use graphene_std::Color;
use graphene_std::raster::Image;
use graphene_std::renderer::RenderMetadata;
use graphene_std::text::Font;
use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::{Color, IntrospectMode};
#[impl_message(Message, Portfolio)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
@ -24,11 +22,24 @@ pub enum PortfolioMessage {
#[child]
Spreadsheet(SpreadsheetMessage),
// Introspected data is cleared after all queued messages which relied on the introspection are complete
ClearIntrospectedData,
// Sends a request to compile the network. Should occur when any value, preference, or font changes
CompileActiveDocument,
// Sends a request to evaluate the network. Should occur when any context value changes.2
// Sends a request to evaluate the network. Should occur when any context value changes.
EvaluateActiveDocument,
// Sends a request to introspect data in the network, and return it to the editor
IntrospectActiveDocument {
inputs_to_introspect: HashSet<CompiledProtonodeInput>,
},
ExportActiveDocument {
file_name: String,
file_type: FileType,
scale_factor: f64,
bounds: ExportBounds,
transparent_background: bool,
},
// Processes the compilation response and updates the data stored in the network interface for the active document
// TODO: Add document ID in response for stability
ProcessCompilationResponse {
@ -36,12 +47,13 @@ pub enum PortfolioMessage {
},
ProcessEvaluationResponse {
evaluation_metadata: RenderMetadata,
},
ProcessIntrospectionResponse {
#[serde(skip)]
introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box<dyn std::any::Any + Send + Sync>)>,
},
ProcessThumbnails {
inputs_to_render: HashSet<CompiledProtonodeInput>,
introspected_inputs: IntrospectionResponse,
},
RenderThumbnails,
ProcessThumbnails,
DocumentPassMessage {
document_id: DocumentId,
message: DocumentMessage,
@ -134,13 +146,6 @@ pub enum PortfolioMessage {
SelectDocument {
document_id: DocumentId,
},
SubmitDocumentExport {
file_name: String,
file_type: FileType,
scale_factor: f64,
bounds: ExportBounds,
transparent_background: bool,
},
ToggleRulers,
UpdateDocumentWidgets,
UpdateOpenDocumentsList,

View file

@ -6,24 +6,29 @@ use crate::application::generate_uuid;
use crate::consts::{DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX};
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
use crate::messages::frontend::utility_types::{ExportBounds, FrontendDocumentDetails};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::DocumentMessageContext;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document_migration::*;
use crate::messages::portfolio::spreadsheet::{InspectInputConnector, SpreadsheetMessageHandlerData};
use crate::messages::portfolio::spreadsheet::SpreadsheetMessageHandlerData;
use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
use crate::node_graph_executor::{CompilationRequest, ExportConfig, NodeGraphExecutor};
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId;
use graphene_std::renderer::Quad;
use graph_craft::document::value::EditorMetadata;
use graph_craft::document::{AbsoluteInputConnector, InputConnector, NodeInput, OutputConnector};
use graphene_std::any::EditorContext;
use graphene_std::application_io::TimingInformation;
use graphene_std::memo::IntrospectMode;
use graphene_std::renderer::{Quad, RenderMetadata};
use graphene_std::text::Font;
use std::vec;
use graphene_std::transform::{Footprint, RenderQuality};
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI};
use std::sync::Arc;
#[derive(ExtractField)]
pub struct PortfolioMessageContext<'a> {
@ -54,9 +59,10 @@ pub struct PortfolioMessageHandler {
pub reset_node_definitions_on_open: bool,
// Data from the node graph. Data for inputs are set to be collected on each evaluation, and added on the evaluation response
// Data from old nodes get deleted after a compilation
pub introspected_input_data: HashMap<CompiledProtonodeInput, Box<dyn std::any::Any + Send + Sync>>,
pub downcasted_input_data: HashMap<CompiledProtonodeInput, TaggedValue>,
pub context_data: HashMap<CompiledProtonodeInput, Context>,
// Always take data after requesting it
pub introspected_data: HashMap<CompiledProtonodeInput, Option<Arc<dyn std::any::Any + Send + Sync>>>,
pub introspected_call_argument: HashMap<CompiledProtonodeInput, Option<Arc<dyn std::any::Any + Send + Sync>>>,
pub previous_thumbnail_data: HashMap<CompiledProtonodeInput, Arc<dyn std::any::Any + Send + Sync>>,
}
#[message_handler_data]
@ -105,13 +111,18 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
self.menu_bar_message_handler.process_message(message, responses, ());
}
PortfolioMessage::Spreadsheet(message) => {
self.spreadsheet.process_message(message, responses, SpreadsheetMessageHandlerData {introspected_data});
self.spreadsheet.process_message(
message,
responses,
SpreadsheetMessageHandlerData {
introspected_data: &self.introspected_data,
},
);
}
PortfolioMessage::Document(message) => {
if let Some(document_id) = self.active_document_id {
if let Some(document) = self.documents.get_mut(&document_id) {
let document_inputs = DocumentMessageContext {
document_id,
let document_inputs = DocumentMessageData {
ipp,
persistent_data: &self.persistent_data,
current_tool,
@ -143,8 +154,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
PortfolioMessage::DocumentPassMessage { document_id, message } => {
if let Some(document) = self.documents.get_mut(&document_id) {
let document_inputs = DocumentMessageContext {
document_id,
let document_inputs = DocumentMessageData {
ipp,
persistent_data: &self.persistent_data,
current_tool,
@ -356,12 +366,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
PortfolioMessage::NewDocumentWithName { name } => {
let mut new_document = DocumentMessageHandler::default();
new_document.name = name;
responses.add(DocumentMessage::PTZUpdate);
let document_id = DocumentId(generate_uuid());
if self.active_document().is_some() {
responses.add(BroadcastEvent::ToolAbort);
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
}
self.load_document(new_document, document_id, responses, false);
@ -664,13 +672,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
if create_document {
// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image
responses.add(Message::StartQueue);
responses.add(Message::StartEvaluationQueue);
responses.add(DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true });
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
responses.add(Message::StartQueue);
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
responses.add(DocumentMessage::ZoomCanvasToFitAll);
responses.add(Message::EndEvaluationQueue);
}
}
PortfolioMessage::PasteSvg {
@ -696,13 +701,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
if create_document {
// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image
responses.add(Message::StartQueue);
responses.add(Message::StartEvaluationQueue);
responses.add(DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true });
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
responses.add(Message::StartQueue);
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
responses.add(DocumentMessage::ZoomCanvasToFitAll);
responses.add(Message::EndEvaluationQueue);
}
}
PortfolioMessage::PrevDocument => {
@ -747,7 +749,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(OverlaysMessage::Draw);
responses.add(BroadcastEvent::ToolAbort);
responses.add(BroadcastEvent::SelectionChanged);
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open });
if node_graph_open {
@ -768,8 +769,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
}
PortfolioMessage::CompileActiveDocument => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", document_id);
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get_mut(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return;
};
if document.network_interface.hash_changed() {
@ -788,37 +789,49 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
},
});
}
// Always evaluate after a recompile
responses.add(PortfolioMessage::EvaluateActiveDocument);
}
PortfolioMessage::ProcessCompilationResponse { compilation_metadata } => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get_mut(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return;
};
for (AbsoluteInputConnector { network_path, connector }, caller) in compilation_metadata.protonode_callers_for_value {
document.network_interface.set_input_caller(connector, caller, &network_path)
for (value_connectors, caller) in compilation_metadata.protonode_caller_for_values {
for AbsoluteInputConnector { network_path, connector } in value_connectors {
let (first, network_path) = network_path.split_first().unwrap();
if first != &NodeId(0) {
continue;
}
document.network_interface.set_input_caller(&connector, caller, network_path)
}
}
for (protonode_path, caller) in compilation_metadata.protonode_callers_for_node {
let (node_id, network_path) = protonode_path.to_vec().split_last().expect("Protonode path cannot be empty");
document.network_interface.set_node_caller(node_id, caller, &network_path)
for (protonode_paths, caller) in compilation_metadata.protonode_caller_for_nodes {
for protonode_path in protonode_paths {
let (first, node_path) = protonode_path.split_first().unwrap();
if first != &NodeId(0) {
continue;
}
let (node_id, network_path) = node_path.split_last().expect("Protonode path cannot be empty");
document.network_interface.set_node_caller(node_id, caller, &network_path)
}
}
for (sni, input_types) in compilation_metadata.types_to_add {
document.network_interface.add_type(sni, input_types);
}
for ((sni, number_of_inputs)) in compilation_metadata.types_to_remove {
// Removed saves type of the document node
let mut cleared_thumbnails = Vec::new();
for (sni, number_of_inputs) in compilation_metadata.types_to_remove {
// Removed saved type of the document node
document.network_interface.remove_type(sni);
// Remove introspection data for all monitor nodes and the thumbnails
let mut cleared_thumbnails = Vec::new();
for monitor_index in 0..number_of_inputs {
self.introspected_input_data.remove((sni, monitor_index));
self.downcasted_input_data.remove((sni, monitor_index));
self.context_data.remove((sni, monitor_index));
cleared_thumbnails.push(NodeId(sni.0+monitor_index as u64 +1));
// Remove all thumbnails
for input_index in 0..number_of_inputs {
cleared_thumbnails.push(NodeId(sni.0 + input_index as u64 + 1));
}
responses.add(FrontendMessage::UpdateThumbnails { add: Vec::new(), clear: cleared_thumbnails })
}
responses.add(FrontendMessage::UpdateThumbnails {
add: Vec::new(),
clear: cleared_thumbnails,
});
// Always evaluate after a recompile
responses.add(PortfolioMessage::EvaluateActiveDocument);
}
PortfolioMessage::EvaluateActiveDocument => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
@ -827,100 +840,54 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
};
// Get all the inputs to save data for. This includes vector modify, thumbnails, and spreadsheet data
let inputs_to_monitor = HashSet::new();
let inputs_to_render = HashSet::new();
let inspect_input = None;
// Get the protonode input for all side layer inputs connected to the export in the document network for thumbnails in the layer panel
for caller in document
.network_interface
.document_metadata()
.all_layers()
.filter_map(|layer| {
let input = InputConnector::Node {
node_id: layer.to_node(),
input_index: 1,
};
document
.network_interface
.downstream_caller_from_input(&input, &[])
}) {
inputs_to_monitor.insert((*caller, IntrospectMode::Data));
inputs_to_render.insert(*caller);
}
// Save data for all inputs in the viewed node graph
if document.graph_view_overlay_open {
let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else {
return;
};
for (export_index, export) in viewed_network.exports.iter().enumerate() {
if let Some(caller) = document
.network_interface
.downstream_caller_from_input(InputConnector::Export(export_index), &document.breadcrumb_network_path)
{
inputs_to_monitor.push((*caller, IntrospectMode::Data))
};
if let Some(NodeInput::Node { node_id, .. }) = export {
for upstream_node in document
.network_interface
.upstream_flow_back_from_nodes(vec![*node_id], &document.breadcrumb_network_path, network_interface::FlowType::UpstreamFlow)
{
let node = viewed_network.nodes[&upstream_node];
for (index, _) in node.inputs.iter().enumerate().filter(|(_, node_input)| node_input.is_exposed()) {
if let Some(caller) = document
.network_interface
.downstream_caller_from_input(InputConnector::Node(node_id, index), &document.breadcrumb_network_path)
{
inputs_to_monitor.insert((*caller, IntrospectMode::Data));
inputs_to_render.insert(*caller);
};
}
}
}
}
}
// Save vector data for all path/transform nodes in the document network
match document.network_interface.input_from_connector(&InputConnector::Export(0), &[]) {
Some(NodeInput::Node { node_id, .. }) => {
for upstream_node in document.network_interface.upstream_flow_back_from_nodes(vec![*node_id], &[], network_interface::FlowType::UpstreamFlow) {
let reference = document.network_interface.reference(node_id, &[]).unwrap_or_default().as_deref().unwrap_or_default();
if reference == "Path" || reference == "Transform" {
let input_connector = InputConnector::Node { node_id, input_index: 0 };
let Some(downstream_caller) = document.network_interface.downstream_caller_from_input(&input_connector, &[]) else{
log::error!("could not get downstream caller for node : {:?}", node_id);
continue;
};
inputs_to_monitor.push(*downstream_caller)
}
}
},
_ => {},
}
// match document.network_interface.input_from_connector(&InputConnector::Export(0), &[]) {
// Some(NodeInput::Node { node_id, .. }) => {
// for upstream_node in document.network_interface.upstream_flow_back_from_nodes(vec![*node_id], &[], network_interface::FlowType::UpstreamFlow) {
// let reference = document.network_interface.reference(&upstream_node, &[]).and_then(|reference| reference.as_deref());
// if reference == Some("Path") || reference == Some("Transform") {
// let input_connector = InputConnector::Node { node_id: *node_id, input_index: 0 };
// let Some(downstream_caller) = document.network_interface.downstream_caller_from_input(&input_connector, &[]) else {
// log::error!("could not get downstream caller for node : {:?}", node_id);
// continue;
// };
// inputs_to_monitor.insert((*downstream_caller, IntrospectMode::Data));
// }
// }
// }
// _ => {}
// }
// Introspect data for the currently selected node (eventually thumbnail) if the spreadsheet view is open
if self.spreadsheet.spreadsheet_view_open {
let selected_network_path = &document.selection_network_path;
// TODO: Replace with selected thumbnail
if let Some(selected_node) = document.network_interface.selected_nodes_in_nested_network(selected_network_path).and_then(|selected_nodes| {
if selected_nodes.0.len() == 1 {
selected_nodes.0.first().copied()
} else {
None
}
}) {
// TODO: Introspect any input rather than just the first input of the selected node
let selected_connector = InputConnector::Node { node_id: selected_node, input_index: 0 };
let Some(caller) = document
.network_interface
.downstream_caller_from_input(&selected_connector, selected_network_path) else {
log::error!("Could not get downstream caller for {:?}", selected_node);
};
inputs_to_monitor.push((*caller, IntrospectMode::Data));
inspect_input = Some(InspectInputConnector { input_connector: AbsoluteInputConnector { network_path: selected_network_path.clone(), connector: selected_connector }, protonode_input: *caller });
}
}
// if self.spreadsheet.spreadsheet_view_open {
// let selected_network_path = &document.selection_network_path;
// // TODO: Replace with selected thumbnail
// if let Some(selected_node) = document
// .network_interface
// .selected_nodes_in_nested_network(selected_network_path)
// .and_then(|selected_nodes| if selected_nodes.0.len() == 1 { selected_nodes.0.first().copied() } else { None })
// {
// // TODO: Introspect any input rather than just the first input of the selected node
// let selected_connector = InputConnector::Node {
// node_id: selected_node,
// input_index: 0,
// };
// match document.network_interface.downstream_caller_from_input(&selected_connector, selected_network_path) {
// Some(caller) => {
// inputs_to_monitor.insert((*caller, IntrospectMode::Data));
// inspect_input = Some(InspectInputConnector {
// input_connector: AbsoluteInputConnector {
// network_path: selected_network_path.clone(),
// connector: selected_connector,
// },
// protonode_input: *caller,
// });
// }
// None => log::error!("Could not get downstream caller for {:?}", selected_connector),
// }
// };
// }
// let animation_time = match animation.timing_information().animation_time {
// AnimationState::Stopped => 0.,
@ -929,67 +896,18 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// };
let mut context = EditorContext::default();
// context.footprint = Some(Footprint {
// transform: document.metadata().document_to_viewport,
// resolution: ipp.viewport_bounds.size().as_uvec2(),
// quality: RenderQuality::Full,
// });
// context.animation_time = Some(animation_time);
// context.real_time = Some(ipp.time);
// context.downstream_transform = Some(DAffine2::IDENTITY);
let render_config = RenderConfig {
viewport: Footprint {
transform: document.metadata().document_to_viewport,
resolution: ipp.viewport_bounds.size().as_uvec2(),
..Default::default()
},
time: animation.timing_information(),
#[cfg(any(feature = "resvg", feature = "vello"))]
export_format: graphene_std::application_io::ExportFormat::Canvas,
#[cfg(not(any(feature = "resvg", feature = "vello")))]
export_format: graphene_std::application_io::ExportFormat::Svg,
view_mode: document.view_mode,
hide_artboards: false,
for_export: false,
};
context.footprint = Some(Footprint {
transform: document.metadata().document_to_viewport,
resolution: ipp.viewport_bounds.size().as_uvec2(),
quality: RenderQuality::Full,
});
context.animation_time = Some(timing_information.animation_time.as_secs_f64());
context.real_time = Some(ipp.time);
context.downstream_transform = Some(DAffine2::IDENTITY);
context.render_config = render_config;
self.executor.submit_node_graph_evaluation(
context,
inputs_to_monitor,
None,
None,
);
// Queue messages to be run after the evaluation returns data for the inputs to monitor
responses.add(Message::StartQueue);
if let Some(inspect_input) = inspect_input {
responses.add(SpreadsheetMessage::UpdateLayout { inpect_input });
}
responses.add(PortfolioMessage::ProcessThumbnails {inputs_to_render});
responses.add(Message::EndQueue);
self.executor.submit_node_graph_evaluation(context, None, None);
}
PortfolioMessage::ProcessEvaluationResponse { evaluation_metadata, introspected_inputs } => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return;
};
for (input, mode, data) in introspected_inputs {
match mode {
IntrospectMode::Input => {
let Some(context) = data.downcast_ref()
self.introspected_input_data.extend(introspected_inputs);
},
IntrospectMode::Data => {
self.introspected_input_data.extend(introspected_inputs);
},
}
}
PortfolioMessage::ProcessEvaluationResponse { evaluation_metadata } => {
let RenderMetadata {
upstream_footprints: footprints,
local_transforms,
@ -1009,25 +927,117 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// AnimationState::Playing { .. } => responses.add(PortfolioMessage::EvaluateActiveDocument),
// _ => {}
// };
},
PortfolioMessage::ProcessThumbnails { inputs_to_render } => {
let mut thumbnail_response = ThumbnailRenderResponse::default();
for thumbnail_input in inputs_to_render {
let monitor_node_id = thumbnail_input.0.0 + thumbnail_input.1 as u64 + 1;
match self.try_render_thumbnail(&thumbnail_input) {
ThumbnailRenderResult::NoChange => {}
ThumbnailRenderResult::ClearThumbnail => thumbnail_response.clear.push(NodeId(monitor_node_id)),
ThumbnailRenderResult::UpdateThumbnail(thumbnail) => {
thumbnail_response.add.push((NodeId(monitor_node_id), thumbnail));
},
// After an evaluation, always render all thumbnails
responses.add(PortfolioMessage::RenderThumbnails);
}
PortfolioMessage::IntrospectActiveDocument { inputs_to_introspect } => {
self.executor.submit_node_graph_introspection(inputs_to_introspect);
}
PortfolioMessage::ProcessIntrospectionResponse { introspected_inputs } => {
for (input, mode, data) in introspected_inputs.0.into_iter() {
match mode {
IntrospectMode::Input => {
self.introspected_call_argument.insert(input, data);
}
IntrospectMode::Data => {
self.introspected_data.insert(input, data);
}
}
}
responses.add(FrontendMessage::UpdateThumbnails { add: thumbnail_response.add, clear: thumbnail_response.clear })
},
PortfolioMessage::ActiveDocumentExport {
}
PortfolioMessage::ClearIntrospectedData => {
self.introspected_call_argument.clear();
self.introspected_data.clear()
}
PortfolioMessage::RenderThumbnails => {
let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get(&document_id)) else {
log::error!("Tried to render non-existent document: {:?}", self.active_document_id);
return;
};
let mut inputs_to_render = HashSet::new();
// Get the protonode input for all side layer inputs connected to the export in the document network for thumbnails in the layer panel
for caller in document.network_interface.document_metadata().all_layers().filter_map(|layer| {
let input = InputConnector::Node {
node_id: layer.to_node(),
input_index: 1,
};
document.network_interface.downstream_caller_from_input(&input, &[])
}) {
inputs_to_render.insert(*caller);
}
// Save data for all inputs in the viewed node graph
if document.graph_view_overlay_open {
let Some(viewed_network) = document.network_interface.nested_network(&document.breadcrumb_network_path) else {
return;
};
for (export_index, export) in viewed_network.exports.iter().enumerate() {
match document
.network_interface
.downstream_caller_from_input(&InputConnector::Export(export_index), &document.breadcrumb_network_path)
{
Some(caller) => {
// inputs_to_monitor.insert((*caller, IntrospectMode::Data));
inputs_to_render.insert(*caller);
}
None => {}
};
if let NodeInput::Node { node_id, .. } = export {
for upstream_node in document
.network_interface
.upstream_flow_back_from_nodes(vec![*node_id], &document.breadcrumb_network_path, network_interface::FlowType::UpstreamFlow)
{
let node = &viewed_network.nodes[&upstream_node];
for (index, _) in node.inputs.iter().enumerate().filter(|(_, node_input)| node_input.is_exposed()) {
if let Some(caller) = document
.network_interface
.downstream_caller_from_input(&InputConnector::node(upstream_node, index), &document.breadcrumb_network_path)
{
// inputs_to_monitor.insert((*caller, IntrospectMode::Data));
inputs_to_render.insert(*caller);
};
}
}
}
}
};
responses.add(PortfolioMessage::IntrospectActiveDocument {
inputs_to_introspect: inputs_to_render,
});
responses.add(Message::StartIntrospectionQueue);
responses.add(PortfolioMessage::ProcessThumbnails);
responses.add(Message::EndIntrospectionQueue);
}
PortfolioMessage::ProcessThumbnails => {
let mut thumbnail_response = ThumbnailRenderResponse::default();
for (thumbnail_input, introspected_data) in self.introspected_data.drain() {
let input_node_id = thumbnail_input.0.0 + thumbnail_input.1 as u64;
let Some(evaluated_data) = introspected_data else {
// Input was not evaluated, do not change its thumbnail
continue;
};
let previous_thumbnail_data = self.previous_thumbnail_data.get(&thumbnail_input);
match graph_craft::document::value::render_thumbnail_if_change(&evaluated_data, previous_thumbnail_data) {
graph_craft::document::value::ThumbnailRenderResult::NoChange => return,
graph_craft::document::value::ThumbnailRenderResult::ClearThumbnail => thumbnail_response.clear.push(NodeId(input_node_id)),
graph_craft::document::value::ThumbnailRenderResult::UpdateThumbnail(thumbnail) => thumbnail_response.add.push((NodeId(input_node_id), thumbnail)),
}
self.previous_thumbnail_data.insert(thumbnail_input, evaluated_data);
}
responses.add(FrontendMessage::UpdateThumbnails {
add: thumbnail_response.add,
clear: thumbnail_response.clear,
})
}
PortfolioMessage::ExportActiveDocument {
file_name,
file_type,
animation_export_data,
scale_factor,
bounds,
transparent_background,
@ -1035,57 +1045,45 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
let document = self.active_document_id.and_then(|id| self.documents.get_mut(&id)).expect("Tried to render non-existent document");
// Update the scope inputs with the render settings
// self.executor.submit_node_graph_compilation(CompilationRequest {
// network: document.network_interface.document_network().clone(),
// font_cache: self.persistent_data.font_cache.clone(),
// editor_metadata: EditorMetadata {
// #[cfg(any(feature = "resvg", feature = "vello"))]
// use_vello: preferences.use_vello(),
// #[cfg(not(any(feature = "resvg", feature = "vello")))]
// use_vello: false,
// hide_artboards: transparent_background,
// for_export: true,
// view_mode: document.view_mode,
// transform_to_viewport: true,
// },
// });
self.executor.submit_node_graph_compilation(CompilationRequest {
network: document.network_interface.document_network().clone(),
font_cache: self.persistent_data.font_cache.clone(),
editor_metadata: EditorMetadata {
#[cfg(any(feature = "resvg", feature = "vello"))]
use_vello: preferences.use_vello(),
#[cfg(not(any(feature = "resvg", feature = "vello")))]
use_vello: false,
hide_artboards: transparent_background,
for_export: true,
view_mode: document.view_mode,
transform_to_viewport: true,
},
});
let document_to_viewport = document.metadata().document_to_viewport;
// Calculate the bounding box of the region to be exported
let document_bounds = match bounds {
let Some(document_bounds) = (match bounds {
ExportBounds::AllArtwork => document.network_interface.document_bounds_document_space(!transparent_background),
ExportBounds::Selection => document.network_interface.selected_bounds_document_space(!transparent_background, &[]),
ExportBounds::Artboard(id) => document.metadata().bounding_box_document(id),
// ExportBounds::Viewport => ipp.document_bounds(document_to_viewport),
}
.ok_or_else(|| "No bounding box".to_string())?;
// ExportBounds::Viewport => ipp.document_bounds(document.metadata().document_to_viewport),
}) else {
log::error!("No bounding box when exporting");
return;
};
let size = document_bounds[1] - document_bounds[0];
let scaled_size = size * scale_factor;
let transform = DAffine2::from_translation(document_bounds[0]).inverse();
let mut context = EditorContext::default();
// context.footprint = Footprint {
// document_to_viewport: DAffine2::from_scale(DVec2::splat(scale_factor)) * transform,
// resolution: scaled_size.as_uvec2(),
// ..Default::default()
// };
// context.real_time = Some(ipp.time);
// context.downstream_transform = Some(DAffine2::IDENTITY);
context.footprint = Some(Footprint {
transform: DAffine2::from_scale(DVec2::splat(scale_factor)) * transform,
resolution: scaled_size.as_uvec2(),
..Default::default()
});
context.real_time = Some(ipp.time);
context.downstream_transform = Some(DAffine2::IDENTITY);
let render_config = RenderConfig {
viewport: Footprint {
transform: DAffine2::from_scale(DVec2::splat(scale_factor)) * transform,
resolution: (size * scale_factor).as_uvec2(),
..Default::default()
},
time: Default::default(),
export_format: graphene_std::application_io::ExportFormat::Svg,
view_mode: document.view_mode,
hide_artboards: transparent_background,
for_export: true,
};
context.render_config = render_config;
// Special handling for exporting the artwork
let file_suffix = &format!(".{file_type:?}").to_lowercase();
let file_name = match file_name.ends_with(FILE_SAVE_SUFFIX) {
@ -1093,28 +1091,18 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
false => file_name + file_suffix,
};
let export_config = ExportConfig {
file_name,
file_type,
scale_factor,
bounds,
transparent_background,
..Default::default()
};
self.executor.submit_node_graph_evaluation(
context,
Vec::new(),
None,
Some(ExportConfig {
file_name,
file_type,
scale_factor,
bounds,
transparent_background,
size: scaled_size,
}),
);
None,
Some(ExportConfig {
file_name,
file_type,
scale_factor,
bounds,
transparent_background,
size: scaled_size,
}),
);
// if let Some((start, end, fps)) = animation_export_data {
// let total_frames = ((start - end) * fps) as u32;
@ -1155,20 +1143,20 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// }
// Reset the scope nodes for hide artboards/hide_artboard name
// self.executor.submit_node_graph_compilation(CompilationRequest {
// network: document.network_interface.document_network().clone(),
// font_cache: self.persistent_data.font_cache.clone(),
// editor_metadata: EditorMetadata {
// #[cfg(any(feature = "resvg", feature = "vello"))]
// use_vello: preferences.use_vello().use_vello,
// #[cfg(not(any(feature = "resvg", feature = "vello")))]
// use_vello: false,
// hide_artboards: false,
// for_export: false,
// view_mode: document.view_mode,
// transform_to_viewport: true,
// },
// });
self.executor.submit_node_graph_compilation(CompilationRequest {
network: document.network_interface.document_network().clone(),
font_cache: self.persistent_data.font_cache.clone(),
editor_metadata: EditorMetadata {
#[cfg(any(feature = "resvg", feature = "vello"))]
use_vello: preferences.use_vello(),
#[cfg(not(any(feature = "resvg", feature = "vello")))]
use_vello: false,
hide_artboards: false,
for_export: false,
view_mode: document.view_mode,
transform_to_viewport: true,
},
});
}
PortfolioMessage::ToggleRulers => {
@ -1181,7 +1169,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
PortfolioMessage::UpdateDocumentWidgets => {
if let Some(document) = self.active_document() {
document.update_document_widgets(responses, animation.is_playing(), animation_time);
document.update_document_widgets(responses, animation.is_playing(), timing_information.animation_time);
}
}
PortfolioMessage::UpdateOpenDocumentsList => {
@ -1333,57 +1321,11 @@ impl PortfolioMessageHandler {
/text>"#
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
.to_string();
responses.add(Message::ProcessQueue((graphene_std::renderer::EvaluationMetadata::default(), Vec::new())));
responses.add(Message::ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata::default()));
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
}
result
}
// Returns an error if the data could not be introspected, returns None if the data type could not be rendered.
fn try_render_thumbnail(&self, protonode_input: &CompiledProtonodeInput) -> ThumbnailRenderResult {
let Ok(introspected_data) = self.introspected_input_data.get(protonode_input) else {
log::error!("Could not introspect node from input: {:?}", protonode_input);
return ThumbnailRenderResult::ClearThumbnail;
};
if let Some(previous_tagged_value) = self.downcasted_input_data.get(protonode_input) {
if previous_tagged_value.compare_value_to_dyn_any(introspected_data) {
return ThumbnailRenderResult::NoChange;
}
}
let Ok(new_tagged_value) = TaggedValue::try_from_std_any_ref(&introspected_data) else {
return ThumbnailRenderResult::ClearThumbnail;
};
let Some(renderable_data) = TaggedValue::as_renderable(&new_tagged_value) else {
// New value is not renderable
return ThumbnailRenderResult::ClearThumbnail;
};
let render_params = RenderParams {
view_mode: ViewMode::Normal,
culling_bounds: bounds,
thumbnail: true,
hide_artboards: false,
for_export: false,
for_mask: false,
alignment_parent_transform: None,
};
// Render the thumbnail data into an SVG string
let mut render = SvgRender::new();
renderable_data.render_svg(&mut render, &render_params);
// Give the SVG a viewbox and outer <svg>...</svg> wrapper tag
let [min, max] = renderable_data.bounding_box(DAffine2::IDENTITY, true).unwrap_or_default();
render.format_svg(min, max);
self.downcasted_input_data.insert(protonode_input, new_tagged_value);
ThumbnailRenderResult::UpdateThumbnail(render.svg.to_svg_string())
}
}
#[derive(Clone, Debug, Default)]
@ -1391,10 +1333,3 @@ pub struct ThumbnailRenderResponse {
add: Vec<(SNI, String)>,
clear: Vec<SNI>,
}
pub enum ThumbnailRenderResult {
NoChange,
// Cleared if there is an error or the data could not be rendered
ClearThumbnail,
UpdateThumbnail(String),
}

View file

@ -1,3 +1,4 @@
use crate::messages::prelude::*;
use graph_craft::document::AbsoluteInputConnector;
use graphene_std::uuid::CompiledProtonodeInput;
@ -7,7 +8,7 @@ use graphene_std::uuid::CompiledProtonodeInput;
pub enum SpreadsheetMessage {
ToggleOpen,
UpdateLayout { inpect_input: InspectInputConnector },
UpdateLayout { inspect_input: InspectInputConnector },
PushToInstancePath { index: usize },
TruncateInstancePath { len: usize },
@ -15,7 +16,7 @@ pub enum SpreadsheetMessage {
ViewVectorDataDomain { domain: VectorDataDomain },
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug)]
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
pub enum VectorDataDomain {
#[default]
Points,

View file

@ -3,21 +3,17 @@ use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup,
use crate::messages::portfolio::spreadsheet::InspectInputConnector;
use crate::messages::prelude::*;
use crate::messages::tool::tool_messages::tool_prelude::*;
use graph_craft::document::{AbsoluteInputConnector, NodeId};
use graphene_std::Color;
use graphene_std::Context;
use graphene_std::GraphicGroupTable;
use graphene_std::instances::Instances;
use graphene_std::memo::IORecord;
use graphene_std::raster::Image;
use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement};
use std::any::Any;
use std::sync::Arc;
pub struct SpreadsheetMessageHandlerData {
pub introspected_data: &HashMap<CompiledProtonodeInput, Box<dyn std::any::Any + Send + Sync>>;
pub struct SpreadsheetMessageHandlerData<'a> {
pub introspected_data: &'a HashMap<CompiledProtonodeInput, Option<Arc<dyn std::any::Any + Send + Sync>>>,
}
/// The spreadsheet UI allows for instance data to be previewed.
@ -35,7 +31,7 @@ pub struct SpreadsheetMessageHandler {
#[message_handler_data]
impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData> for SpreadsheetMessageHandler {
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, data: SpreadsheetMessageHandlerData) {
let {introspected_data} = data;
let SpreadsheetMessageHandlerData { introspected_data } = data;
match message {
SpreadsheetMessage::ToggleOpen => {
self.spreadsheet_view_open = !self.spreadsheet_view_open;
@ -48,12 +44,12 @@ impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData> for Sprea
}
// Update checked UI state for open
responses.add(MenuBarMessage::SendLayout);
self.update_layout(responses);
self.update_layout(introspected_data, responses);
}
// Queued on introspection request, runs on introspection response when the data has been sent back to the editor
SpreadsheetMessage::UpdateLayout { inpect_input } => {
self.inspect_input = Some(inpect_input);
SpreadsheetMessage::UpdateLayout { inspect_input } => {
self.inspect_input = Some(inspect_input);
self.update_layout(introspected_data, responses);
}
@ -79,7 +75,7 @@ impl MessageHandler<SpreadsheetMessage, SpreadsheetMessageHandlerData> for Sprea
}
impl SpreadsheetMessageHandler {
fn update_layout(&mut self, introspected_data: &HashMap<CompiledProtonodeInput, Box<dyn std::any::Any + Send + Sync>>, responses: &mut VecDeque<Message>) {
fn update_layout(&mut self, introspected_data: &HashMap<CompiledProtonodeInput, Option<Arc<dyn std::any::Any + Send + Sync>>>, responses: &mut VecDeque<Message>) {
responses.add(FrontendMessage::UpdateSpreadsheetState {
// The node is sent when the data is available
node: None,
@ -94,18 +90,20 @@ impl SpreadsheetMessageHandler {
breadcrumbs: Vec::new(),
vector_data_domain: self.viewing_vector_data_domain,
};
let mut layout = match self.inspect_input {
let mut layout = match &self.inspect_input {
Some(inspect_input) => {
match introspected_data.get(&inspect_input.protonode_input){
Some(data) => {
match generate_layout(instrospected_data, &mut layout_data) {
match introspected_data.get(&inspect_input.protonode_input) {
Some(data) => match data {
Some(instrospected_data) => match generate_layout(instrospected_data, &mut layout_data) {
Some(layout) => layout,
None => label("The introspected data is not a supported type to be displayed."),
}
},
None => label("Introspected data is not available for this input. This input may be cached."),
},
None => label("Introspected data is not available for this input. This input may be cached."),
// There should always be an entry for each protonode input. If its empty then it was not requested or an error occured
None => label("Error getting introspected data"),
}
},
}
None => label("No input selected to show data for."),
};
@ -130,7 +128,7 @@ struct LayoutData<'a> {
vector_data_domain: VectorDataDomain,
}
fn generate_layout(introspected_data: &Box<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
// We simply try random types. TODO: better strategy.
#[allow(clippy::manual_map)]
if let Some(io) = introspected_data.downcast_ref::<ArtboardGroupTable>() {

View file

@ -2,7 +2,7 @@ use graphene_std::text::FontCache;
#[derive(Debug, Default)]
pub struct PersistentData {
pub font_cache: Arc<FontCache>,
pub font_cache: std::sync::Arc<FontCache>,
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]

View file

@ -20,10 +20,8 @@ impl PreferencesMessageHandler {
self.selection_mode
}
pub fn editor_preferences(&self) -> EditorPreferences {
EditorPreferences {
use_vello: self.use_vello && self.supports_wgpu(),
}
pub fn use_vello(&self) -> bool {
self.use_vello && self.supports_wgpu()
}
pub fn supports_wgpu(&self) -> bool {

View file

@ -3,16 +3,15 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::message::Message;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
use crate::messages::prelude::Responses;
use crate::messages::prelude::{PortfolioMessage, Responses};
use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_polygon_parameters, inside_polygon, inside_star, polygon_outline, polygon_vertex_position, star_outline};
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position};
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{InputConnector, NodeInput};
use std::collections::VecDeque;
use std::f64::consts::TAU;

View file

@ -3,15 +3,15 @@ use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::message::Message;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector};
use crate::messages::prelude::FrontendMessage;
use crate::messages::portfolio::document::{overlays::utility_types::OverlayContext};
use crate::messages::prelude::{FrontendMessage, PortfolioMessage};
use crate::messages::prelude::Responses;
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
use crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline};
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position};
use glam::DVec2;
use graph_craft::document::NodeInput;
use graph_craft::document::{InputConnector, NodeInput};
use graph_craft::document::value::TaggedValue;
use std::collections::VecDeque;
use std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_4, PI, SQRT_2};

View file

@ -1,12 +1,12 @@
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, NodeTemplate};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
use crate::messages::prelude::*;
use bezier_rs::Subpath;
use glam::DVec2;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::document::{InputConnector, NodeInput};
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Color;
use graphene_std::NodeInputDecleration;
@ -154,8 +154,9 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
});
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartQueue);
responses.add(Message::StartEvaluationQueue);
responses.add(PenToolMessage::RecalculateLatestPointsPosition);
responses.add(Message::EndEvaluationQueue);
}
/// Merge the `first_endpoint` with `second_endpoint`.

View file

@ -3,11 +3,11 @@ use super::*;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
use crate::messages::portfolio::document::utility_types::network_interface::{ NodeTemplate};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::tool_messages::tool_prelude::*;
use glam::DAffine2;
use graph_craft::document::NodeInput;
use graph_craft::document::{InputConnector, NodeInput};
use graph_craft::document::value::TaggedValue;
use std::collections::VecDeque;

View file

@ -3,14 +3,14 @@ use crate::consts::{BOUNDS_SELECT_THRESHOLD, LINE_ROTATE_SNAP_ANGLE};
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use crate::messages::tool::common_functionality::graph_modification_utils;
pub use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapTypeConfiguration};
use crate::messages::tool::tool_messages::shape_tool::ShapeToolData;
use crate::messages::tool::tool_messages::tool_prelude::*;
use glam::DVec2;
use graph_craft::document::NodeInput;
use graph_craft::document::{InputConnector, NodeInput};
use graph_craft::document::value::TaggedValue;
use std::collections::VecDeque;

View file

@ -5,7 +5,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDial;
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDialState;
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandle;
@ -16,6 +16,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGiz
use crate::messages::tool::common_functionality::shapes::shape_utility::polygon_outline;
use crate::messages::tool::tool_messages::tool_prelude::*;
use glam::DAffine2;
use graph_craft::document::InputConnector;
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use std::collections::VecDeque;

View file

@ -3,11 +3,11 @@ use super::*;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::tool_messages::tool_prelude::*;
use glam::DAffine2;
use graph_craft::document::NodeInput;
use graph_craft::document::{InputConnector, NodeInput};
use graph_craft::document::value::TaggedValue;
use std::collections::VecDeque;

View file

@ -2,7 +2,6 @@ use super::ShapeToolData;
use crate::messages::message::Message;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage, Responses};
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
@ -11,7 +10,7 @@ use crate::messages::tool::tool_messages::tool_prelude::Key;
use crate::messages::tool::utility_types::*;
use bezier_rs::Subpath;
use glam::{DAffine2, DMat2, DVec2};
use graph_craft::document::NodeInput;
use graph_craft::document::{InputConnector, NodeInput};
use graph_craft::document::value::TaggedValue;
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::misc::dvec2_to_point;

View file

@ -4,7 +4,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::{NumberOfPointsDial, NumberOfPointsDialState};
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState};
use crate::messages::tool::common_functionality::graph_modification_utils;
@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGi
use crate::messages::tool::tool_messages::tool_prelude::*;
use core::f64;
use glam::DAffine2;
use graph_craft::document::NodeInput;
use graph_craft::document::{InputConnector, NodeInput};
use graph_craft::document::value::TaggedValue;
use std::collections::VecDeque;

View file

@ -10,8 +10,8 @@ use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;
use crate::messages::tool::common_functionality::snapping::SnapData;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::common_functionality::transformation_cage::*;
use graph_craft::document::NodeId;
use graphene_std::renderer::Quad;
use graphene_std::uuid::NodeId;
#[derive(Default, ExtractField)]
pub struct ArtboardTool {

View file

@ -5,11 +5,11 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue;
use graphene_std::Color;
use graphene_std::brush::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
use graphene_std::raster::BlendMode;
use graphene_std::uuid::NodeId;
const BRUSH_MAX_SIZE: f64 = 5000.;
@ -379,8 +379,9 @@ impl Fsm for BrushToolFsmState {
else {
new_brush_layer(document, responses);
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::StartQueue);
responses.add(Message::StartEvaluationQueue);
responses.add(BrushToolMessage::DragStart);
responses.add(Message::EndEvaluationQueue);
BrushToolFsmState::Ready
}
}

View file

@ -8,7 +8,7 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::utility_functions::should_extend;
use glam::DVec2;
use graph_craft::document::NodeId;
use graphene_std::uuid::NodeId;
use graphene_std::Color;
use graphene_std::vector::VectorModificationType;
use graphene_std::vector::{PointId, SegmentId};
@ -251,7 +251,6 @@ impl Fsm for FreehandToolFsmState {
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
responses.add(Message::StartQueue);
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
tool_data.layer = Some(layer);

View file

@ -643,12 +643,12 @@ impl PathToolData {
self.drag_start_pos = input.mouse.position;
if input.time - self.last_click_time > DOUBLE_CLICK_MILLISECONDS {
if input.time as u64 - self.last_click_time > DOUBLE_CLICK_MILLISECONDS {
self.saved_points_before_anchor_convert_smooth_sharp.clear();
self.stored_selection = None;
}
self.last_click_time = input.time;
self.last_click_time = input.time as u64;
let old_selection = shape_editor.selected_points().cloned().collect::<Vec<_>>();

View file

@ -12,8 +12,8 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, closest_point, should_extend};
use bezier_rs::{Bezier, BezierHandles};
use graph_craft::document::NodeId;
use graphene_std::Color;
use graphene_std::uuid::NodeId;
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData};
use graphene_std::vector::{PointId, VectorModificationType};
@ -1258,9 +1258,10 @@ impl PenToolData {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
// This causes the following message to be run only after the next graph evaluation runs and the transforms are updated
responses.add(Message::StartQueue);
responses.add(Message::StartEvaluationQueue);
// It is necessary to defer this until the transform of the layer can be accurately computed (quite hacky)
responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport });
responses.add(Message::EndEvaluationQueue);
}
/// Perform extension of an existing path

View file

@ -22,11 +22,11 @@ use crate::messages::tool::common_functionality::transformation_cage::*;
use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage};
use bezier_rs::Subpath;
use glam::DMat2;
use graph_craft::document::NodeId;
use graphene_std::path_bool::BooleanOperation;
use graphene_std::renderer::Quad;
use graphene_std::renderer::Rect;
use graphene_std::transform::ReferencePoint;
use graphene_std::uuid::NodeId;
use std::fmt;
#[derive(Default, ExtractField)]

View file

@ -3,7 +3,6 @@ use crate::consts::{DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE};
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager;
@ -19,9 +18,10 @@ use crate::messages::tool::common_functionality::snapping::{self, SnapCandidateP
use crate::messages::tool::common_functionality::transformation_cage::{BoundingBoxManager, EdgeBool};
use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::document::{InputConnector, NodeInput};
use graphene_std::Color;
use graphene_std::renderer::Quad;
use graphene_std::uuid::NodeId;
use graphene_std::vector::misc::ArcType;
use std::vec;
@ -599,17 +599,16 @@ impl Fsm for ShapeToolFsmState {
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses);
responses.add(Message::StartQueue);
match tool_data.current_shape {
ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => {
responses.add(Message::StartEvaluationQueue);
responses.add(GraphOperationMessage::TransformSet {
layer,
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
transform_in: TransformIn::Viewport,
skip_rerender: false,
});
responses.add(Message::EndEvaluationQueue);
tool_options.fill.apply_fill(layer, responses);
}
ShapeType::Line => {
@ -617,6 +616,7 @@ impl Fsm for ShapeToolFsmState {
tool_data.line_data.editing_layer = Some(layer);
}
}
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);

View file

@ -10,7 +10,8 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio
use crate::messages::tool::common_functionality::graph_modification_utils::{self, find_spline, merge_layers, merge_points};
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint};
use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend};
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::document::NodeInput;
use graphene_std::uuid::NodeId;
use graphene_std::Color;
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
@ -360,8 +361,6 @@ impl Fsm for SplineToolFsmState {
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
tool_data.current_layer = Some(layer);
responses.add(Message::StartQueue);
SplineToolFsmState::Drawing
}
(SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => {

View file

@ -5,7 +5,6 @@ use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_RED, DRAG_THRESHOLD};
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
@ -14,10 +13,11 @@ use crate::messages::tool::common_functionality::snapping::{self, SnapCandidateP
use crate::messages::tool::common_functionality::transformation_cage::*;
use crate::messages::tool::common_functionality::utility_functions::text_bounding_box;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::document::{InputConnector, NodeInput};
use graphene_std::Color;
use graphene_std::renderer::Quad;
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
use graphene_std::uuid::NodeId;
use graphene_std::vector::style::Fill;
#[derive(Default, ExtractField)]
@ -381,7 +381,7 @@ impl TextToolData {
parent: document.new_layer_parent(true),
insert_index: 0,
});
responses.add(Message::StartQueue);
responses.add(Message::StartEvaluationQueue);
responses.add(GraphOperationMessage::FillSet {
layer: self.layer,
fill: if editing_text.color.is_some() {
@ -394,15 +394,14 @@ impl TextToolData {
layer: self.layer,
transform: editing_text.transform,
transform_in: TransformIn::Viewport,
skip_rerender: true,
skip_rerender: false,
});
self.editing_text = Some(editing_text);
self.set_editing(true, font_cache, responses);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });
responses.add(PortfolioMessage::CompileActiveDocument);
responses.add(Message::EndEvaluationQueue);
}
fn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, font_cache: &FontCache) -> Option<LayerNodeIdentifier> {

View file

@ -1,32 +1,18 @@
use std::sync::Arc;
use crate::consts::FILE_SAVE_SUFFIX;
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::frontend::utility_types::FileType;
use crate::messages::prelude::*;
use dyn_any::DynAny;
use glam::DAffine2;
use graph_craft::document::value::{NetworkOutput, TaggedValue};
use graph_craft::document::{
AbsoluteInputConnector, AbsoluteOutputConnector, CompilationMetadata, CompiledNodeMetadata, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, generate_uuid,
};
use graph_craft::document::value::{EditorMetadata, RenderOutput, TaggedValue};
use graph_craft::document::{CompilationMetadata, DocumentNode, NodeNetwork, generate_uuid};
use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::{EditorCompilationMetadata, EditorEvaluationMetadata, EditorMetadata};
use graphene_std::application_io::{CompilationMetadata, TimingInformation};
use graphene_std::application_io::{EditorEvaluationMetadata, NodeGraphUpdateMessage};
use graphene_std::any::EditorContext;
use graphene_std::memo::IntrospectMode;
use graphene_std::renderer::{EvaluationMetadata, format_transform_matrix};
use graphene_std::renderer::{RenderMetadata, RenderSvgSegmentList};
use graphene_std::renderer::{RenderParams, SvgRender};
use graphene_std::renderer::format_transform_matrix;
use graphene_std::text::FontCache;
use graphene_std::transform::{Footprint, RenderQuality};
use graphene_std::uuid::{CompiledProtonodeInput, ProtonodePath, SNI};
use graphene_std::vector::VectorData;
use graphene_std::vector::style::ViewMode;
use graphene_std::wasm_application_io::NetworkOutput;
use graphene_std::{CompiledProtonodeInput, OwnedContextImpl, SNI};
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI};
mod runtime_io;
use interpreted_executor::dynamic_executor::{EditorContext, ResolvedDocumentNodeMetadata};
pub use runtime_io::NodeRuntimeIO;
mod runtime;
@ -35,7 +21,7 @@ pub use runtime::*;
#[derive(Clone, Debug, Default, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct CompilationRequest {
pub network: NodeNetwork,
// Data which is avaialable from scope inputs (currently WasmEditorApi, but will be split)
// Data which is available from scope inputs
pub font_cache: Arc<FontCache>,
pub editor_metadata: EditorMetadata,
}
@ -46,27 +32,34 @@ pub struct CompilationResponse {
}
// Metadata the editor sends when evaluating the network
#[derive(Debug, Default, DynAny)]
#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)]
pub struct EvaluationRequest {
pub evaluation_id: u64,
pub inputs_to_monitor: Vec<(CompiledProtonodeInput, IntrospectMode)>,
#[serde(skip)]
pub context: EditorContext,
// pub custom_node_to_evaluate: Option<SNI>,
pub node_to_evaluate: Option<SNI>,
}
// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
pub struct EvaluationResponse {
evaluation_id: u64,
result: Result<TaggedValue, String>,
introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box<dyn std::any::Any + Send + Sync>)>,
// TODO: Handle transforming node graph output in the node graph itself
transform: DAffine2,
}
#[derive(Debug, Clone, Default)]
pub struct IntrospectionResponse(pub Vec<((NodeId, usize), IntrospectMode, Option<Arc<dyn std::any::Any + Send + Sync>>)>);
impl PartialEq for IntrospectionResponse {
fn eq(&self, _other: &Self) -> bool {
false
}
}
// #[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
pub enum NodeGraphUpdate {
CompilationResponse(CompilationResponse),
EvaluationResponse(EvaluationResponse),
IntrospectionResponse(IntrospectionResponse),
}
#[derive(Debug, Default)]
@ -98,6 +91,7 @@ impl NodeGraphExecutor {
let node_runtime = NodeRuntime::new(request_receiver, response_sender);
let node_executor = Self {
busy: false,
futures: HashMap::new(),
runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
};
@ -118,34 +112,39 @@ impl NodeGraphExecutor {
/// Compile the network
pub fn submit_node_graph_compilation(&mut self, compilation_request: CompilationRequest) {
self.runtime_io.send(GraphRuntimeRequest::CompilationRequest(compilation_request)).map_err(|e| e.to_string());
if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::CompilationRequest(compilation_request)) {
log::error!("Could not send evaluation request. {:?}", error);
return;
}
}
/// Adds an evaluate request for whatever current network is cached.
pub fn submit_node_graph_evaluation(
&mut self,
context: EditorContext,
inputs_to_monitor: Vec<(CompiledProtonodeInput, IntrospectMode)>,
custom_node_to_evaluate: Option<SNI>,
export_config: Option<ExportConfig>,
) {
/// Adds an evaluation request for whatever current network is cached.
pub fn submit_node_graph_evaluation(&mut self, context: EditorContext, node_to_evaluate: Option<SNI>, export_config: Option<ExportConfig>) {
let evaluation_id = generate_uuid();
self.runtime_io.send(GraphRuntimeRequest::EvaluationRequest(editor_evaluation_request)).map_err(|e| e.to_string());
if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::EvaluationRequest(EvaluationRequest {
evaluation_id,
context,
node_to_evaluate,
})) {
log::error!("Could not send evaluation request. {:?}", error);
return;
}
let evaluation_context = EvaluationContext { export_config };
self.futures.insert(evaluation_id, evaluation_context);
}
pub fn submit_node_graph_introspection(&mut self, nodes_to_introspect: HashSet<CompiledProtonodeInput>) {
if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::IntrospectionRequest(nodes_to_introspect)) {
log::error!("Could not send evaluation request. {:?}", error);
return;
}
}
// Continuously poll the executor (called by request animation frame)
pub fn poll_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Result<(), String> {
// Moved into portfolio message handler, since this is where the introspected inputs are saved
for response in self.runtime_io.receive() {
match response {
NodeGraphUpdate::EvaluationResponse(EvaluationResponse {
evaluation_id,
result,
transform,
introspected_inputs,
}) => {
NodeGraphUpdate::EvaluationResponse(EvaluationResponse { evaluation_id, result }) => {
responses.add(OverlaysMessage::Draw);
let node_graph_output = match result {
@ -160,18 +159,14 @@ impl NodeGraphExecutor {
let render_output = match node_graph_output {
TaggedValue::RenderOutput(render_output) => render_output,
value => {
return Err("Incorrect render type for exporting (expected NetworkOutput)".to_string());
return Err(format!("Incorrect render type for exporting {:?} (expected NetworkOutput)", value.ty()));
}
};
let evaluation_context = self.futures.remove(&evaluation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
if let Some(export_config) = evaluation_context.export_config {
// Export
let TaggedValue::RenderOutput(RenderOutput {
data: graphene_std::wasm_application_io::RenderOutputType::Svg(svg),
..
}) = node_graph_output
else {
let graphene_std::wasm_application_io::RenderOutputType::Svg(svg) = render_output.data else {
return Err("Incorrect render type for exporting (expected RenderOutput::Svg)".to_string());
};
@ -193,7 +188,7 @@ impl NodeGraphExecutor {
}
} else {
// Update artwork
self.process_node_graph_output(render_output, introspected_inputs, transform, responses);
self.process_node_graph_output(render_output, responses)?
}
}
NodeGraphUpdate::CompilationResponse(compilation_response) => {
@ -213,58 +208,35 @@ impl NodeGraphExecutor {
Ok(result) => result,
};
responses.add(PortfolioMessage::ProcessCompilationResponse { compilation_metadata });
responses.add(NodeGraphMessage::SendGraph);
}
NodeGraphUpdate::IntrospectionResponse(introspection_response) => {
responses.add(Message::ProcessIntrospectionQueue(introspection_response));
}
}
}
Ok(())
}
fn process_node_graph_output(
&mut self,
node_graph_output: TaggedValue,
introspected_inputs: Vec<(CompiledProtonodeInput, IntrospectMode, Box<dyn std::any::Any + Send + Sync>)>,
transform: DAffine2,
responses: &mut VecDeque<Message>,
) -> Result<(), String> {
let mut render_output_metadata = RenderMetadata::default();
match node_graph_output {
TaggedValue::RenderOutput(render_output) => {
match render_output.data {
graphene_std::wasm_application_io::RenderOutputType::Svg(svg) => {
// Send to frontend
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
graphene_std::wasm_application_io::RenderOutputType::CanvasFrame(frame) => {
let matrix = format_transform_matrix(frame.transform);
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{}\"", matrix) };
let svg = format!(
r#"<svg><foreignObject width="{}" height="{}"{transform}><div data-canvas-placeholder="canvas{}"></div></foreignObject></svg>"#,
frame.resolution.x, frame.resolution.y, frame.surface_id.0
);
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
_ => {
return Err(format!("Invalid node graph output type: {:#?}", render_output.data));
}
}
render_output_metadata = render_output.metadata;
fn process_node_graph_output(&self, render_output: RenderOutput, responses: &mut VecDeque<Message>) -> Result<(), String> {
match render_output.data {
graphene_std::wasm_application_io::RenderOutputType::Svg(svg) => {
// Send to frontend
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
graphene_std::wasm_application_io::RenderOutputType::CanvasFrame(frame) => {
let matrix = format_transform_matrix(frame.transform);
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{}\"", matrix) };
let svg = format!(
r#"<svg><foreignObject width="{}" height="{}"{transform}><div data-canvas-placeholder="canvas{}"></div></foreignObject></svg>"#,
frame.resolution.x, frame.resolution.y, frame.surface_id.0
);
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
// TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::DVec2(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::VectorData(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::GraphicGroup(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::RasterData(render_object) => Self::debug_render(render_object, transform, responses),
// TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
_ => {
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
return Err(format!("Invalid node graph output type: {:#?}", render_output.data));
}
};
responses.add(Message::ProcessQueue((render_output_metadata, introspected_inputs)));
}
responses.add(Message::ProcessEvaluationQueue(render_output.metadata));
Ok(())
}
}
@ -404,3 +376,35 @@ impl NodeGraphExecutor {
// }
// }
// }
// Passed as a scope input
#[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct EditorMetadata {
// pub imaginate_hostname: String,
pub use_vello: bool,
pub hide_artboards: bool,
// If exporting, hide the artboard name and do not collect metadata
pub for_export: bool,
pub view_mode: graphene_core::vector::style::ViewMode,
pub transform_to_viewport: bool,
}
unsafe impl dyn_any::StaticType for EditorMetadata {
type Static = EditorMetadata;
}
impl Default for EditorMetadata {
fn default() -> Self {
Self {
// imaginate_hostname: "http://localhost:7860/".into(),
#[cfg(target_arch = "wasm32")]
use_vello: false,
#[cfg(not(target_arch = "wasm32"))]
use_vello: true,
hide_artboards: false,
for_export: false,
view_mode: graphene_core::vector::style::ViewMode::Normal,
transform_to_viewport: true,
}
}
}

View file

@ -1,24 +1,12 @@
use super::*;
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use glam::{DAffine2, DVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeNetwork};
use graph_craft::graphene_compiler::Compiler;
use glam::DVec2;
use graph_craft::document::NodeNetwork;
use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences;
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Context;
use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_std::instances::Instance;
use graphene_std::memo::IORecord;
use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender};
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_std::text::FontCache;
use graphene_std::uuid::{CompiledProtonodeInput, NodeId};
use graphene_std::vector::style::ViewMode;
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
use graphene_std::uuid::CompiledProtonodeInput;
use graphene_std::wasm_application_io::WasmApplicationIo;
use interpreted_executor::dynamic_executor::DynamicExecutor;
use interpreted_executor::util::wrap_network_in_scope;
use once_cell::sync::Lazy;
use spin::Mutex;
@ -41,9 +29,6 @@ pub struct NodeRuntime {
node_graph_errors: GraphErrors,
/// Which node is inspected and which monitor node is used (if any) for the current execution
inspect_state: Option<InspectState>,
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
@ -61,10 +46,10 @@ pub enum GraphRuntimeRequest {
// Renders thumbnails for the data from the last execution
// If the upstream node stores data for the context override, then another evaluation must be performed at the input
// This is performed separately from execution requests, since thumbnails for animation should be updated once every 50ms or so.
ThumbnailRenderRequest(HashSet<CompiledProtonodeInput>),
// ThumbnailRenderRequest(HashSet<CompiledProtonodeInput>),
// Request the data from a list of node inputs. For example, used by vector modify to get the data at the input of every Path node.
// Can also be used by the spreadsheet/introspection system
IntrospectionRequest(HashSet<(CompiledProtonodeInput, IntrospectMode)>),
IntrospectionRequest(HashSet<CompiledProtonodeInput>),
}
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
@ -87,6 +72,9 @@ impl NodeGraphRuntimeSender {
fn send_evaluation_response(&self, response: EvaluationResponse) {
self.0.send(NodeGraphUpdate::EvaluationResponse(response)).expect("Failed to send evaluation response")
}
fn send_introspection_response(&self, response: IntrospectionResponse) {
self.0.send(NodeGraphUpdate::IntrospectionResponse(response)).expect("Failed to send introspection response")
}
}
pub static NODE_RUNTIME: Lazy<Mutex<Option<NodeRuntime>>> = Lazy::new(|| Mutex::new(None));
@ -103,119 +91,103 @@ impl NodeRuntime {
node_graph_errors: Vec::new(),
substitutions: preprocessor::generate_node_substitutions(),
thumbnail_render_tagged_values: HashSet::new(),
inspect_state: None,
}
}
pub async fn run(&mut self) {
if self.application_io.is_none() {
#[cfg(not(test))]
// #[cfg(not(test))]
self.application_io = Some(Arc::new(WasmApplicationIo::new().await));
#[cfg(test)]
self.application_io = Some(Arc::new(WasmApplicationIo::new_offscreen().await));
// #[cfg(test)]
// self.application_io = Some(Arc::new(WasmApplicationIo::new_offscreen().await));
}
// TODO: This deduplication of messages will probably cause more issues than it solved
// let mut graph = None;
// let mut execution = None;
// let mut thumbnails = None;
// let mut introspection = None;
// for request in self.receiver.try_iter() {
// match request {
// GraphRuntimeRequest::CompilationRequest(_) => graph = Some(request),
// GraphRuntimeRequest::EvaluationRequest(_) => execution = Some(request),
// GraphRuntimeRequest::ThumbnailRenderResponse(_) => thumbnails = Some(request),
// GraphRuntimeRequest::IntrospectionResponse(_) => introspection = Some(request),
// }
// }
// let requests = [font, preferences, graph, execution].into_iter().flatten();
// TODO: This deduplication of messages will probably cause issues
let mut compilation = None;
let mut evaluation = None;
let mut introspection = None;
for request in self.receiver.try_iter() {
match request {
GraphRuntimeRequest::CompilationRequest(CompilationRequest {
mut network,
font_cache,
editor_metadata,
}) => {
GraphRuntimeRequest::CompilationRequest(_) => compilation = Some(request),
GraphRuntimeRequest::EvaluationRequest(_) => evaluation = Some(request),
GraphRuntimeRequest::IntrospectionRequest(_) => introspection = Some(request),
}
}
let requests = [compilation, evaluation, introspection].into_iter().flatten();
for request in requests {
match request {
GraphRuntimeRequest::CompilationRequest(CompilationRequest { network, font_cache, editor_metadata }) => {
// Insert the monitor node to manage the inspection
// self.inspect_state = inspect_node.map(|inspect| InspectState::monitor_inspect_node(&mut network, inspect));
self.node_graph_errors.clear();
let result = self.update_network(network).await;
let result = self.update_network(network, font_cache, editor_metadata).await;
self.sender.send_compilation_response(CompilationResponse {
result,
node_graph_errors: self.node_graph_errors.clone(),
});
}
// Inputs to monitor is sent from the editor, and represents a list of input connectors to track the data through
// During the execution. If the value is None, then the node was not evaluated, which can occur due to caching
GraphRuntimeRequest::EvaluationRequest(EvaluationRequest {
evaluation_id,
context,
inputs_to_monitor,
// custom_node_to_evaluate
node_to_evaluate,
}) => {
for (protonode_input, introspect_mode) in inputs_to_monitor {
self.executor.set_introspect(protonode_input, introspect_mode)
}
let transform = context.render_config.viewport.transform;
// for (protonode_input, introspect_mode) in &inputs_to_monitor {
// self.executor.set_introspect(*protonode_input, *introspect_mode)
// }
let result = self.executor.evaluate_from_node(context, node_to_evaluate).await;
let result = self.execute_network(render_config).await;
let introspected_inputs = Vec::new();
for (protonode_input, mode) in inputs_to_introspect {
let Ok(introspected_data) = self.executor.introspect(protonode_input, mode) else {
log::error!("Could not introspect node from input: {:?}", protonode_input);
continue;
self.sender.send_evaluation_response(EvaluationResponse { evaluation_id, result });
}
// GraphRuntimeRequest::ThumbnailRenderRequest(_) => {}
GraphRuntimeRequest::IntrospectionRequest(inputs) => {
let mut introspected_inputs = Vec::new();
for protonode_input in inputs {
let introspected_data = match self.executor.introspect(protonode_input, IntrospectMode::Data) {
Ok(introspected_data) => introspected_data,
Err(e) => {
log::error!("Could not introspect input: {:?}, error: {:?}", protonode_input, e);
continue;
}
};
introspected_inputs.push((protonode_input, mode, introspected_data));
introspected_inputs.push((protonode_input, IntrospectMode::Data, introspected_data));
}
self.sender.send_evaluation_response(EvaluationResponse {
evaluation_id,
result,
transform,
introspected_inputs,
});
}
GraphRuntimeRequest::ThumbnailRenderRequest(input_to_render) => {
let mut thumbnail_response = ThumbnailRenderResponse::default();
for input in input_to_render {}
self.sender.send_thumbnail_render_response(thumbnail_response);
}
GraphRuntimeRequest::IntrospectionRequest(inputs_to_introspect) => {
self.sender.send_introspection_response(introspection_response);
self.sender.send_introspection_response(IntrospectionResponse(introspected_inputs));
}
}
}
}
async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<CompilationMetadata, String> {
async fn update_network(&mut self, mut graph: NodeNetwork, font_cache: Arc<FontCache>, editor_metadata: EditorMetadata) -> Result<CompilationMetadata, String> {
preprocessor::expand_network(&mut graph, &self.substitutions);
// Creates a network where the node paths to the document network are prefixed with NodeId(0)
let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());
let mut scoped_network = wrap_network_in_scope(graph, font_cache, editor_metadata, self.application_io.as_ref().unwrap().clone());
// We assume only one output
assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled");
// Modifies the NodeNetwork so the tagged values are removed and the document nodes with protonode implementations have their protonode ids set
// Needs to return a mapping of absolute input connectors to protonode callers, types for protonodes, and callers for protonodes, add/remove delta for resolved types
let (proto_network, protonode_callers_for_value, protonode_callers_for_node) = match scoped_network.flatten() {
let (proto_network, protonode_caller_for_values, protonode_caller_for_nodes) = match scoped_network.flatten() {
Ok(network) => network,
Err(e) => {
log::error!("Error compiling network: {e:?}");
return;
return Err(e);
}
};
assert_ne!(proto_network.len(), 0, "No proto nodes exist?");
let result = match self.executor.update(proto_network).await {
Ok((types_to_add, types_to_remove)) => {
// Used to remove thumbnails from the mapping of SNI to rendered SVG strings on the frontend, which occurs when the SNI is removed
// When native frontend rendering is possible, the strings can just be stored in the network interface for each protonode with the rest of the type metadata
Ok(CompilationMetadata {
protonode_callers_for_value,
protonode_callers_for_node,
protonode_caller_for_values,
protonode_caller_for_nodes,
types_to_add,
types_to_remove,
})
@ -225,23 +197,8 @@ impl NodeRuntime {
Err(format!("{e:?}"))
}
};
}
async fn execute_network(&mut self, render_config: RenderConfig) -> Result<TaggedValue, String> {
use graph_craft::graphene_compiler::Executor;
let result = match self.executor.input_type() {
Some(t) if t == concrete!(RenderConfig) => (&self.executor).execute(render_config).await.map_err(|e| e.to_string()),
Some(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()),
Some(t) => Err(format!("Invalid input type {t:?}")),
_ => Err(format!("No input type:\n{:?}", self.node_graph_errors)),
};
let result = match result {
Ok(value) => value,
Err(e) => return Err(e),
};
Ok(result)
// log::debug!("result: {:?}", result);
result
}
}

View file

@ -613,16 +613,25 @@
<div class="wires">
<svg>
{#each $nodeGraph.wires.values() as map}
{#each map.values() as { pathString, dataType, thick, dashed }}
{#each map.values() as { pathString, dataType, thick, dashed, center, monitorSni }}
{#if !thick}
<path
d={pathString}
class="wire-path"
style:--data-line-width={"2px"}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
/>
{/if}
<!-- {console.log("center: ", center)} -->
<!-- {console.log("monitorSni: ", monitorSni)} -->
{#if center && monitorSni}
<!-- {console.log("thumbnails: ", $nodeGraph.thumbnails.get(monitorSni))} -->
<g transform={`translate(${center[0]}, ${center[1]})`}>
{@html $nodeGraph.thumbnails.get(monitorSni)}
</g>
{/if}
{/each}
{/each}
{#if $nodeGraph.wirePathInProgress}
@ -888,7 +897,7 @@
height: 100%;
overflow: visible;
path {
.wire-path {
fill: none;
stroke: var(--data-color-dim);
stroke-width: var(--data-line-width);

View file

@ -321,6 +321,9 @@ export class WirePath {
readonly dataType!: FrontendGraphDataType;
readonly thick!: boolean;
readonly dashed!: boolean;
@TupleToVec2
readonly center!: XY | undefined;
readonly inputSni!: bigint;
}
export class WireUpdate {
@ -1683,7 +1686,7 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateNodeGraphTransform,
UpdateNodeGraphControlBarLayout,
UpdateNodeGraphSelection,
UpdateThumbnails: UpdateThumbnail,
UpdateThumbnails,
UpdateOpenDocumentsList,
UpdatePropertyPanelSectionsLayout,
UpdateSpreadsheetLayout,

View file

@ -118,6 +118,7 @@ export function createNodeGraphState(editor: Editor) {
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => {
// console.log(updateNodeGraphNodes);
update((state) => {
state.nodes.clear();
updateNodeGraphNodes.nodes.forEach((node) => {
@ -168,14 +169,16 @@ export function createNodeGraphState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateThumbnails, (updateThumbnail) => {
editor.subscriptions.subscribeJsMessage(UpdateThumbnails, (updateThumbnails) => {
// console.log("thumbnail update: ", updateThumbnails);
update((state) => {
for (const [id, value] of updateThumbnail.add) {
for (const [id, value] of updateThumbnails.add) {
state.thumbnails.set(id, value);
}
for (const id of updateThumbnail.clear) {
for (const id of updateThumbnails.clear) {
state.thumbnails.set(id, "");
}
// console.log("thumbnails: ", state.thumbnails);
return state;
});
});

View file

@ -15,8 +15,8 @@ use editor::messages::portfolio::document::utility_types::network_interface::Imp
use editor::messages::portfolio::utility_types::Platform;
use editor::messages::prelude::*;
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
use graph_craft::document::NodeId;
use graphene_std::raster::color::Color;
use graphene_std::uuid::NodeId;
use serde::Serialize;
use serde_wasm_bindgen::{self, from_value};
use std::cell::RefCell;

View file

@ -486,6 +486,21 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
None
}
pub fn center(&self) -> Option<DVec2> {
let len = self.manipulator_groups.len();
if len == 0 {
return None;
} else if len % 2 == 1 {
return Some(self.manipulator_groups[len / 2].anchor);
} else {
let p0 = self.manipulator_groups[len / 2 - 1].anchor;
let p1 = self.manipulator_groups[len / 2 - 1].out_handle;
let p2 = self.manipulator_groups[len / 2].in_handle;
let p3 = self.manipulator_groups[len / 2].anchor;
Some(0.125 * p0 + 0.375 * p1.unwrap_or(p0) + 0.375 * p2.unwrap_or(p3) + 0.125 * p3)
}
}
/// Returns the necessary information to create a round join with the provided center.
/// The returned items correspond to:
/// - The `out_handle` for the last manipulator group of `self`

View file

@ -123,6 +123,21 @@ pub fn downcast<'a, V: StaticType + 'a>(i: Box<dyn DynAny<'a> + 'a>) -> Result<B
}
}
#[cfg(feature = "alloc")]
pub fn try_downcast<'a, V: StaticType + 'a>(i: Box<dyn DynAny<'a> + 'a>) -> Result<Box<V>, Box<dyn DynAny<'a> + 'a>> {
let type_id = DynAny::type_id(i.as_ref());
if type_id == core::any::TypeId::of::<<V as StaticType>::Static>() {
// SAFETY: caller guarantees that T is the correct type
let ptr = Box::into_raw(i) as *mut V;
Ok(unsafe { Box::from_raw(ptr) })
} else {
if type_id == core::any::TypeId::of::<&dyn DynAny<'static>>() {
panic!("downcast error: type_id == core::any::TypeId::of::<dyn DynAny<'a>>()");
}
Err(i)
}
}
pub unsafe trait StaticType {
type Static: 'static + ?Sized;
fn type_id(&self) -> core::any::TypeId {

View file

@ -1,6 +1,5 @@
use dyn_any::{DynAny, StaticType, StaticTypeSized};
use glam::{DAffine2, UVec2};
use graphene_core::text::FontCache;
use graphene_core::transform::Footprint;
use graphene_core::vector::style::ViewMode;
use std::fmt::Debug;
@ -236,69 +235,23 @@ pub struct RenderConfig {
pub for_export: bool,
}
struct Logger;
#[derive(Default, Clone, Debug)]
pub struct ApplicationIoValue<Io>(pub Option<Arc<Io>>);
impl NodeGraphUpdateSender for Logger {
fn send(&self, message: NodeGraphUpdateMessage) {
log::warn!("dispatching message with fallback node graph update sender {:?}", message);
}
unsafe impl<T: StaticTypeSized> StaticType for ApplicationIoValue<T> {
type Static = ApplicationIoValue<T::Static>;
}
struct DummyPreferences;
impl<T> Eq for ApplicationIoValue<T> {}
impl GetEditorPreferences for DummyPreferences {
fn use_vello(&self) -> bool {
false
}
}
pub struct EditorApi<Io> {
/// Font data (for rendering text) made available to the graph through the [`WasmEditorApi`].
pub font_cache: FontCache,
/// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web).
pub application_io: Option<Arc<Io>>,
pub node_graph_message_sender: Box<dyn NodeGraphUpdateSender + Send + Sync>,
/// Editor preferences made available to the graph through the [`WasmEditorApi`].
pub editor_preferences: Box<dyn GetEditorPreferences + Send + Sync>,
}
impl<Io> Eq for EditorApi<Io> {}
impl<Io: Default> Default for EditorApi<Io> {
fn default() -> Self {
Self {
font_cache: FontCache::default(),
application_io: None,
node_graph_message_sender: Box::new(Logger),
editor_preferences: Box::new(DummyPreferences),
}
}
}
impl<Io> Hash for EditorApi<Io> {
impl<T> Hash for ApplicationIoValue<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.font_cache.hash(state);
self.application_io.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state);
(self.node_graph_message_sender.as_ref() as *const dyn NodeGraphUpdateSender).hash(state);
(self.editor_preferences.as_ref() as *const dyn GetEditorPreferences).hash(state);
self.0.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state);
}
}
impl<Io> PartialEq for EditorApi<Io> {
impl<T> PartialEq for ApplicationIoValue<T> {
fn eq(&self, other: &Self) -> bool {
self.font_cache == other.font_cache
&& self.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize)
&& std::ptr::eq(self.node_graph_message_sender.as_ref() as *const _, other.node_graph_message_sender.as_ref() as *const _)
&& std::ptr::eq(self.editor_preferences.as_ref() as *const _, other.editor_preferences.as_ref() as *const _)
self.0.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.0.as_ref().map_or(0, |io| addr_of!(io) as usize)
}
}
impl<T> Debug for EditorApi<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EditorApi").field("font_cache", &self.font_cache).finish()
}
}
unsafe impl<T: StaticTypeSized> StaticType for EditorApi<T> {
type Static = EditorApi<T::Static>;
}

View file

@ -1,4 +1,4 @@
use crate::{Ctx, ExtractAnimationTime, ExtractTime};
use crate::{Ctx, ExtractAnimationTime, ExtractRealTime};
const DAY: f64 = 1000. * 3600. * 24.;
@ -21,8 +21,8 @@ pub enum AnimationTimeMode {
}
#[node_macro::node(category("Animation"))]
fn real_time(ctx: impl Ctx + ExtractTime, _primary: (), mode: RealTimeMode) -> f64 {
let time = ctx.try_time().unwrap_or_default();
fn real_time(ctx: impl Ctx + ExtractRealTime, _primary: (), mode: RealTimeMode) -> f64 {
let time = ctx.try_real_time().unwrap_or_default();
// TODO: Implement proper conversion using and existing time implementation
match mode {
RealTimeMode::Utc => time,

View file

@ -1,13 +1,13 @@
use glam::{DAffine2, UVec2};
use crate::transform::Footprint;
use std::any::Any;
use std::borrow::Borrow;
use std::panic::Location;
use std::sync::Arc;
pub trait Ctx: Clone + Send {}
pub trait ExtractFootprint {
#[track_caller]
fn try_footprint(&self) -> Option<&Footprint>;
#[track_caller]
fn footprint(&self) -> &Footprint {
@ -18,8 +18,12 @@ pub trait ExtractFootprint {
}
}
pub trait ExtractTime {
fn try_time(&self) -> Option<f64>;
pub trait ExtractDownstreamTransform {
fn try_downstream_transform(&self) -> Option<&DAffine2>;
}
pub trait ExtractRealTime {
fn try_real_time(&self) -> Option<f64>;
}
pub trait ExtractAnimationTime {
@ -42,9 +46,32 @@ pub trait CloneVarArgs: ExtractVarArgs {
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>>;
}
pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractAnimationTime + ExtractVarArgs {}
pub trait ExtractAll: ExtractFootprint + ExtractDownstreamTransform + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs {}
impl<T: ?Sized + ExtractFootprint + ExtractIndex + ExtractTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {}
impl<T: ?Sized + ExtractFootprint + ExtractDownstreamTransform + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {}
#[derive(Debug, Clone, PartialEq)]
pub enum ContextDependency {
ExtractFootprint,
// Can be used by cull nodes to check if the final output would be outside the footprint viewport
ExtractDownstreamTransform,
ExtractRealTime,
ExtractAnimationTime,
ExtractIndex,
ExtractVarArgs,
}
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, Eq)]
pub enum VarArgsResult {
@ -72,17 +99,30 @@ impl<T: ExtractFootprint + Sync> ExtractFootprint for Option<T> {
fn try_footprint(&self) -> Option<&Footprint> {
self.as_ref().and_then(|x| x.try_footprint())
}
#[track_caller]
fn footprint(&self) -> &Footprint {
self.try_footprint().unwrap_or_else(|| {
log::warn!("trying to extract footprint from context None {} ", Location::caller());
&Footprint::DEFAULT
})
}
impl ExtractDownstreamTransform for () {
fn try_downstream_transform(&self) -> Option<&DAffine2> {
log::error!("tried to extract downstream transform form (), {}", Location::caller());
None
}
}
impl<T: ExtractTime + Sync> ExtractTime for Option<T> {
fn try_time(&self) -> Option<f64> {
self.as_ref().and_then(|x| x.try_time())
impl<T: ExtractDownstreamTransform + Ctx + Sync + Send> ExtractDownstreamTransform for &T {
fn try_downstream_transform(&self) -> Option<&DAffine2> {
(*self).try_downstream_transform()
}
}
impl<T: ExtractDownstreamTransform + Sync> ExtractDownstreamTransform for Option<T> {
fn try_downstream_transform(&self) -> Option<&DAffine2> {
self.as_ref().and_then(|x| x.try_downstream_transform())
}
}
impl<T: ExtractRealTime + Sync> ExtractRealTime for Option<T> {
fn try_real_time(&self) -> Option<f64> {
self.as_ref().and_then(|x| x.try_real_time())
}
}
impl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Option<T> {
@ -111,9 +151,16 @@ impl<T: ExtractFootprint + Sync> ExtractFootprint for Arc<T> {
(**self).try_footprint()
}
}
impl<T: ExtractTime + Sync> ExtractTime for Arc<T> {
fn try_time(&self) -> Option<f64> {
(**self).try_time()
impl<T: ExtractDownstreamTransform + Sync> ExtractDownstreamTransform for Arc<T> {
fn try_downstream_transform(&self) -> Option<&DAffine2> {
(**self).try_downstream_transform()
}
}
impl<T: ExtractRealTime + Sync> ExtractRealTime for Arc<T> {
fn try_real_time(&self) -> Option<f64> {
(**self).try_real_time()
}
}
impl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Arc<T> {
@ -156,43 +203,22 @@ impl<T: CloneVarArgs + Sync> CloneVarArgs for Arc<T> {
}
}
impl Ctx for ContextImpl<'_> {}
impl Ctx for Arc<OwnedContextImpl> {}
impl ExtractFootprint for ContextImpl<'_> {
fn try_footprint(&self) -> Option<&Footprint> {
self.footprint
}
}
impl ExtractTime for ContextImpl<'_> {
fn try_time(&self) -> Option<f64> {
self.time
}
}
impl ExtractIndex for ContextImpl<'_> {
fn try_index(&self) -> Option<Vec<usize>> {
self.index.clone()
}
}
impl ExtractVarArgs for ContextImpl<'_> {
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };
inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds).copied()
}
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };
Ok(inner.len())
}
}
impl ExtractFootprint for OwnedContextImpl {
fn try_footprint(&self) -> Option<&Footprint> {
self.footprint.as_ref()
}
}
impl ExtractTime for OwnedContextImpl {
fn try_time(&self) -> Option<f64> {
impl ExtractDownstreamTransform for OwnedContextImpl {
fn try_downstream_transform(&self) -> Option<&DAffine2> {
self.downstream_transform.as_ref()
}
}
impl ExtractRealTime for OwnedContextImpl {
fn try_real_time(&self) -> Option<f64> {
self.real_time
}
}
@ -234,20 +260,26 @@ impl CloneVarArgs for Arc<OwnedContextImpl> {
}
}
// Lifetime isnt necessary?
pub type Context<'a> = Option<Arc<OwnedContextImpl>>;
type DynRef<'a> = &'a (dyn Any + Send + Sync);
type DynBox = Box<dyn Any + Send + Sync>;
#[derive(dyn_any::DynAny)]
pub struct OwnedContextImpl {
// The footprint represents the document to viewport render metadata
footprint: Option<Footprint>,
varargs: Option<Arc<[DynBox]>>,
parent: Option<Arc<dyn ExtractVarArgs + Sync + Send>>,
// This could be converted into a single enum to save extra bytes
index: Option<Vec<usize>>,
// The transform node does not modify the document to viewport, it instead modifies this,
// which can be used to transform the evaluated data from the node and check if it is within the
// document to viewport transform.
downstream_transform: Option<DAffine2>,
index: Option<usize>,
real_time: Option<f64>,
animation_time: Option<f64>,
// varargs: Option<(Vec<String>, Arc<[DynBox]>)>,
varargs: Option<Arc<[DynBox]>>,
parent: Option<Arc<dyn ExtractVarArgs + Sync + Send>>,
}
impl std::fmt::Debug for OwnedContextImpl {
@ -285,8 +317,9 @@ impl OwnedContextImpl {
#[track_caller]
pub fn from<T: ExtractAll + CloneVarArgs>(value: T) -> Self {
let footprint = value.try_footprint().copied();
let downstream_transform = value.try_downstream_transform().copied();
let index = value.try_index();
let time = value.try_time();
let time = value.try_real_time();
let frame_time = value.try_animation_time();
let parent = match value.varargs_len() {
Ok(x) if x > 0 => value.arc_clone(),
@ -294,21 +327,40 @@ impl OwnedContextImpl {
};
OwnedContextImpl {
footprint,
varargs: None,
parent,
downstream_transform,
index,
real_time: time,
animation_time: frame_time,
varargs: None,
parent,
}
}
pub const fn empty() -> Self {
OwnedContextImpl {
footprint: None,
varargs: None,
parent: None,
downstream_transform: None,
index: None,
real_time: None,
animation_time: None,
varargs: None,
parent: None,
}
}
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
}
}
}
}
}
@ -317,10 +369,31 @@ impl OwnedContextImpl {
pub fn set_footprint(&mut self, footprint: Footprint) {
self.footprint = Some(footprint);
}
pub fn set_downstream_transform(&mut self, transform: DAffine2) {
self.downstream_transform = Some(transform);
}
pub fn try_apply_downstream_transform(&mut self, transform: DAffine2) {
if let Some(downstream_transform) = self.downstream_transform {
self.downstream_transform = Some(downstream_transform * transform);
}
}
pub fn set_real_time(&mut self, time: f64) {
self.real_time = Some(time);
}
pub fn set_animation_time(&mut self, animation_time: f64) {
self.animation_time = Some(animation_time);
}
pub fn set_index(&mut self, index: usize) {
self.index = Some(index);
}
pub fn with_footprint(mut self, footprint: Footprint) -> Self {
self.footprint = Some(footprint);
self
}
pub fn with_downstream_transform(mut self, downstream_transform: DAffine2) -> Self {
self.downstream_transform = Some(downstream_transform);
self
}
pub fn with_real_time(mut self, time: f64) -> Self {
self.real_time = Some(time);
self
@ -329,11 +402,6 @@ impl OwnedContextImpl {
self.animation_time = Some(animation_time);
self
}
pub fn with_vararg(mut self, value: Box<dyn Any + Send + Sync>) -> Self {
assert!(self.varargs.is_none_or(|value| value.is_empty()));
self.varargs = Some(Arc::new([value]));
self
}
pub fn with_index(mut self, index: usize) -> Self {
if let Some(current_index) = &mut self.index {
current_index.push(index);
@ -345,31 +413,193 @@ impl OwnedContextImpl {
pub fn into_context(self) -> Option<Arc<Self>> {
Some(Arc::new(self))
}
pub fn add_vararg(mut self, _variable_name: String, value: Box<dyn Any + Send + Sync>) -> Self {
assert!(self.varargs.is_none_or(|value| value.is_empty()));
// self.varargs = Some((vec![variable_name], Arc::new([value])));
self.varargs = Some(Arc::new([value]));
self
}
pub fn set_varargs(&mut self, var_args: (Vec<String>, Arc<[DynBox]>)) {
self.varargs = Some(var_args.1)
}
pub fn with_vararg(mut self, var_args: (impl Into<String>, DynBox)) -> Self {
self.varargs = Some(Arc::new([var_args.1]));
self
}
pub fn erase_parent(mut self) -> Self {
self.parent = None;
self
}
}
#[derive(Default, Clone, dyn_any::DynAny)]
pub struct ContextImpl<'a> {
pub(crate) footprint: Option<&'a Footprint>,
varargs: Option<&'a [DynRef<'a>]>,
// This could be converted into a single enum to save extra bytes
index: Option<Vec<usize>>,
time: Option<f64>,
// #[derive(Default, Clone, Copy, dyn_any::DynAny)]
// pub struct ContextImpl<'a> {
// pub(crate) footprint: Option<&'a Footprint>,
// varargs: Option<&'a [DynRef<'a>]>,
// // This could be converted into a single enum to save extra bytes
// index: Option<usize>,
// time: Option<f64>,
// }
// impl<'a> ContextImpl<'a> {
// pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f>
// where
// 'a: 'f,
// {
// ContextImpl {
// footprint: Some(new_footprint),
// varargs: varargs.map(|x| x.borrow()),
// ..*self
// }
// }
// }
#[node_macro::node(category("Context Getter"))]
fn get_footprint(ctx: impl Ctx + ExtractFootprint) -> Option<Footprint> {
ctx.try_footprint().copied()
}
impl<'a> ContextImpl<'a> {
pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f>
where
'a: 'f,
{
ContextImpl {
footprint: Some(new_footprint),
varargs: varargs.map(|x| x.borrow()),
index: self.index.clone(),
..*self
}
}
#[node_macro::node(category("Context Getter"))]
fn get_document_to_viewport(ctx: impl Ctx + ExtractFootprint) -> Option<DAffine2> {
ctx.try_footprint().map(|footprint| footprint.transform.clone())
}
#[node_macro::node(category("Context Getter"))]
fn get_resolution(ctx: impl Ctx + ExtractFootprint) -> Option<UVec2> {
ctx.try_footprint().map(|footprint| footprint.resolution.clone())
}
#[node_macro::node(category("Context Getter"))]
fn get_downstream_transform(ctx: impl Ctx + ExtractDownstreamTransform) -> Option<DAffine2> {
ctx.try_downstream_transform().copied()
}
#[node_macro::node(category("Context Getter"))]
fn get_real_time(ctx: impl Ctx + ExtractRealTime) -> Option<f64> {
ctx.try_real_time()
}
#[node_macro::node(category("Context Getter"))]
fn get_animation_time(ctx: impl Ctx + ExtractAnimationTime) -> Option<f64> {
ctx.try_animation_time()
}
#[node_macro::node(category("Context Getter"))]
fn get_index(ctx: impl Ctx + ExtractIndex) -> Option<u32> {
ctx.try_index().map(|index| index as u32)
}
// #[node_macro::node(category("Loop"))]
// async fn loop_node<T: Default>(
// ctx: impl Ctx + CloneVarArgs + ExtractAll,
// #[implementations(
// Context -> Option<u32>,
// )]
// return_if_some: impl Node<Context<'static>, Output = Option<T>>,
// #[implementations(
// Context -> (),
// )]
// run_if_none: impl Node<Context<'static>, Output = ()>,
// ) -> T {
// let mut context = OwnedContextImpl::from(ctx.clone());
// context.arc_mutex = Some(Arc::new(Mutex::new(None)));
// loop {
// if let Some(return_value) = return_if_some.eval(context.clone().into_context()).await {
// return return_value;
// }
// run_if_none.eval(context.clone().into_context()).await;
// let Some(context_after_loop) = context.arc_mutex.unwrap().lock().unwrap().take() else {
// log::error!("Loop context was not set, breaking loop to avoid infinite loop");
// return T::default();
// };
// context = context_after_loop;
// context.arc_mutex = Some(Arc::new(Mutex::new(None)));
// }
// }
// #[node_macro::node(category("Loop"))]
// async fn update_loop_node_context(ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync) -> () {
// let mut context = OwnedContextImpl::from(ctx.clone());
// let context_after_loop = OwnedContextImpl::from(ctx.clone());
// if let Some(arc_mutex) = context.arc_mutex.as_ref() {
// *arc_mutex.lock().unwrap() = Some(context_after_loop);
// }
// }
#[node_macro::node(category("Loop"))]
async fn set_index<T: 'n + 'static>(
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
#[expose]
#[implementations(
Context -> u32,
Context -> (),
)]
input: impl Node<Context<'static>, Output = T>,
number: u32,
) -> T {
let mut new_context = OwnedContextImpl::from(ctx);
new_context.index = Some(number.try_into().unwrap());
input.eval(new_context.into_context()).await
}
// #[node_macro::node(category("Loop"))]
// fn create_arc_mutex(_ctx: impl Ctx) -> Arc<Mutex<Option<OwnedContextImpl>>> {
// Arc::new(Mutex::new(0))
// }
// #[node_macro::node(category("Loop"))]
// fn get_arc_mutex(ctx: impl Ctx + ExtractArcMutex) -> Option<Arc<Mutex<Option<OwnedContextImpl>>>> {
// ctx.try_arc_mutex()
// }
// #[node_macro::node(category("Loop"))]
// async fn set_arc_mutex<T: 'n + 'static>(
// ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
// // Auto generate for each tagged value type
// #[expose]
// #[implementations(
// Context -> u32,
// )]
// input: impl Node<Context<'static>, Output = T>,
// arc_mutex: Arc<Mutex<Option<OwnedContextImpl>>>,
// ) -> T {
// let mut new_context = OwnedContextImpl::from(ctx);
// new_context.arc_mutex = Some(arc_mutex);
// input.eval(new_context.into_context()).await
// }
// // TODO: Discard node + return () for loop if none branch
// #[node_macro::node(category("Loop"))]
// fn set_arc_mutex_value<T>(_ctx: impl Ctx, #[implementations(Arc<Mutex<u32>>)] arc_mutex: Arc<Mutex<T>>, #[implementations(u32)] value: T) -> () {
// let mut guard = arc_mutex.lock().unwrap(); // lock the mutex
// *guard = value;
// }
// #[node_macro::node(category("Loop"))]
// fn get_context(ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync) -> OwnedContextImpl {
// OwnedContextImpl::from(ctx)
// }
// #[node_macro::node(category("Loop"))]
// fn discard(_ctx: impl Ctx, _input: u32) -> () {}
#[node_macro::node(category("Debug"))]
fn is_none<T>(_: impl Ctx, #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>)] input: Option<T>) -> bool {
input.is_none()
}
// #[node_macro::node(category("Debug"))]
// fn unwrap<T>(_: impl Ctx, #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>, Option<Vec<u32>>)] input: Option<T>) -> T {
// input.unwrap()
// }
#[node_macro::node(category("Debug"))]
fn to_option<T>(_: impl Ctx, boolean: bool, #[implementations(u32)] input: T) -> Option<T> {
boolean.then(|| input)
}
#[node_macro::node(category("Debug"))]
fn to_usize(_: impl Ctx, u32: u32) -> usize {
u32.try_into().unwrap()
}

View file

@ -62,7 +62,7 @@ pub trait Node<'i, Input> {
/// 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<Box<dyn std::any::Any + Send + Sync>> {
fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
log::warn!("Node::introspect not implemented for {}", std::any::type_name::<Self>());
None
}

View file

@ -7,6 +7,88 @@ use std::ops::Deref;
use std::sync::Arc;
use std::sync::Mutex;
/// Caches the output of a given Node and acts as a proxy
#[derive(Default)]
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>>,
}
impl<'i, I: Hash + 'i + std::fmt::Debug, T: 'static + Clone + Send + Sync, CachedNode: 'i> Node<'i, I> for MonitorMemoNode<T, CachedNode>
where
CachedNode: for<'any_input> Node<'any_input, I>,
for<'a> <CachedNode as Node<'a, I>>::Output: Future<Output = T> + WasmNotSend,
{
// 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);
let hash = hasher.finish();
if let Some(data) = self.cache.lock().as_ref().unwrap().as_ref().and_then(|data| (data.0 == hash).then_some(data.1.clone())) {
let cloned_data = (*data).clone();
Box::pin(async move { cloned_data })
} else {
let fut = self.node.eval(input);
let cache = self.cache.clone();
*self.changed_since_last_eval.lock().unwrap() = true;
Box::pin(async move {
let value = fut.await;
*cache.lock().unwrap() = Some((hash, Arc::new(value.clone())));
value
})
}
}
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
}
}
}
impl<T, CachedNode> MonitorMemoNode<T, CachedNode> {
pub fn new(node: CachedNode) -> MonitorMemoNode<T, CachedNode> {
MonitorMemoNode {
cache: Default::default(),
node,
changed_since_last_eval: Arc::new(Mutex::new(true)),
}
}
}
/// Caches the output of a given Node and acts as a proxy
#[derive(Default)]
pub struct MemoNode<T, CachedNode> {
@ -107,7 +189,7 @@ pub mod impure_memo {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode");
}
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum IntrospectMode {
Input,
Data,
@ -117,8 +199,8 @@ pub enum IntrospectMode {
#[derive(Default)]
pub struct MonitorNode<I, O, N> {
#[allow(clippy::type_complexity)]
input: Arc<Mutex<Option<Box<I>>>>,
output: Arc<Mutex<Option<Box<O>>>>,
input: Arc<Mutex<Option<Arc<I>>>>,
output: Arc<Mutex<Option<Arc<O>>>>,
// Gets set to true by the editor when before evaluating the network, then reset when the monitor node is evaluated
introspect_input: Arc<Mutex<bool>>,
introspect_output: Arc<Mutex<bool>>,
@ -137,12 +219,12 @@ where
let output = self.node.eval(input.clone()).await;
let mut introspect_input = self.introspect_input.lock().unwrap();
if *introspect_input {
*self.input.lock().unwrap() = Some(Box::new(input));
*self.input.lock().unwrap() = Some(Arc::new(input));
*introspect_input = false;
}
let mut introspect_output = self.introspect_output.lock().unwrap();
if *introspect_output {
*self.output.lock().unwrap() = Some(Box::new(output.clone()));
*self.output.lock().unwrap() = Some(Arc::new(output.clone()));
*introspect_output = false;
}
output
@ -150,10 +232,10 @@ where
}
// 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<Box<dyn std::any::Any + Send + Sync>> {
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 Box<dyn std::any::Any + Send + Sync>),
IntrospectMode::Data => self.output.lock().unwrap().take().map(|output| output as Box<dyn std::any::Any + Send + Sync>),
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>),
}
}

View file

@ -109,6 +109,8 @@ 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()));
#[cfg(not(target_arch = "wasm32"))]
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;
#[cfg(target_arch = "wasm32")]
@ -132,7 +134,7 @@ pub type TypeErasedPinned<'n> = Pin<Box<TypeErasedNode<'n>>>;
pub type SharedNodeContainer = std::sync::Arc<NodeContainer>;
pub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>;
pub type MonitorConstructor = fn(SharedNodeContainer) -> TypeErasedBox<'static>;
pub type CacheConstructor = fn(SharedNodeContainer) -> TypeErasedBox<'static>;
#[derive(Clone)]
pub struct NodeContainer {
@ -277,17 +279,24 @@ where
type Output = FutureAny<'input>;
#[inline]
fn eval(&'input self, input: Any<'input>) -> Self::Output {
let node_name = std::any::type_name::<N>();
let output = |input| {
let result = self.node.eval(input);
async move { Box::new(result.await) as Any<'input> }
};
match dyn_any::downcast(input) {
Ok(input) => Box::pin(output(*input)),
Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, node_name),
Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, std::any::type_name::<N>()),
}
}
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 reset(&self) {
self.node.reset();
}

View file

@ -1,4 +1,4 @@
use crate::registry::Node;
use crate::Node;
use std::marker::PhantomData;
/// This is how we can generically define composition of two nodes.

View file

@ -91,5 +91,5 @@ pub type SNI = NodeId;
// An input of a compiled protonode, used to reference thumbnails, which are stored on a per input basis
pub type CompiledProtonodeInput = (NodeId, usize);
// Path to the protonode in the document network
pub type ProtonodePath = Box<[NodeId]>;
// Path to the protonode in the wrapped network (document network prefixed with NodeId(0))
pub type ProtonodePath = Vec<NodeId>;

View file

@ -22,7 +22,7 @@ async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + '
let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point);
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(("Transformed point", Box::new(transformed_point)));
let generated_instance = instance.eval(new_ctx.into_context()).await;
for mut instanced in generated_instance.instance_iter() {

View file

@ -1,16 +1,17 @@
pub mod value;
use crate::document::value::TaggedValue;
use crate::proto::{ConstructionArgs, NodeConstructionArgs, OriginalLocation, ProtoNode};
use crate::proto::{ConstructionArgs, NodeConstructionArgs, NodeValueArgs, 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 rustc_hash::FxHashMap;
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
/// Utility function for providing a default boolean value to serde.
@ -509,94 +510,170 @@ impl NodeNetwork {
/// Functions for compiling the network
impl NodeNetwork {
// Returns a topologically sorted vec of protonodes, as well as metadata extracted during compilation
// 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<(Vec<ProtoNode>, Vec<(AbsoluteInputConnector, CompiledProtonodeInput)>, Vec<(ProtonodePath, CompiledProtonodeInput)>), String> {
pub fn flatten(
&mut self,
) -> Result<
(
ProtoNetwork,
Vec<(Vec<AbsoluteInputConnector>, CompiledProtonodeInput)>,
Vec<(Vec<ProtonodePath>, CompiledProtonodeInput)>,
),
String,
> {
// These three arrays are stored in parallel
let mut protonetwork = Vec::new();
let mut value_connectors = Vec::new();
let mut protonode_paths = Vec::new();
let mut calling_protonodes = HashMap::new();
// This function creates a flattened network with populated original location fields but unmapped inputs
// 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
self.traverse_input(
&mut protonetwork,
&mut value_connectors,
&mut protonode_paths,
&mut calling_protonodes,
&mut HashMap::new(),
AbsoluteInputConnector::traversal_start(),
(0, 0),
);
let mut protonode_indices = HashMap::new();
self.traverse_input(&mut protonetwork, &mut HashMap::new(), &mut protonode_indices, AbsoluteInputConnector::traversal_start(), None);
let mut generated_snis = HashSet::new();
// 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()).rev() {
let protonode = protonetwork.get_mut(protonode_index).unwrap();
if let ConstructionArgs::Nodes(NodeConstructionArgs { inputs: input_snis, .. }) = &protonode.construction_args {
for input_sni in input_snis {
assert_ne!(
*input_sni,
NodeId(0),
"All inputs should be mapped to a stable node index, and the calling nodes inputs should be updated"
);
}
}
use std::hash::Hasher;
let mut hasher = rustc_hash::FxHasher::default();
protonode.construction_args.hash(&mut hasher);
let mut stable_node_id = NodeId(hasher.finish());
// The stable node index must be unique for every protonode. If it has the same hash as another protonode, continue hashing itself
// For example two cache nodes connected to a Context getter node have two cache different values, even though the stable node id is the same.
while !generated_snis.insert(stable_node_id) {
stable_node_id.hash(&mut hasher);
stable_node_id = NodeId(hasher.finish());
}
protonode.stable_node_id = stable_node_id;
for (calling_node_index, input_index) in calling_protonodes.get(&protonode_index).unwrap() {
match &mut protonetwork.get_mut(*calling_node_index).unwrap().construction_args {
ConstructionArgs::Nodes(nodes) => {
*nodes.inputs.get_mut(*input_index).unwrap() = stable_node_id;
for protonode_index in 0..protonetwork.len() {
let ProtonodeEntry::Protonode(protonode) = protonetwork.get_mut(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());
}
}
// TODO: Implement for extract
_ => unreachable!(),
}
// 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<_>>(),
)
}
// If none then the upstream node is a Value node, so do not nullify the context
None => upstream_metadata.context_dependencies = Some(Vec::new()),
}
}
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)
};
// This runs for all protonodes
for (caller_path, input_index) in callers {
let caller_index = protonode_indices[&caller_path];
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(),
})
}
}
// Value node cannot be a caller
ConstructionArgs::Value(_) => unreachable!(),
ConstructionArgs::Inline(_) => todo!(),
}
}
}
// Do another traversal now that the caller SNI have been generated to collect metadata for the editor
// 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_index, (value_connector, protonode_path)) in value_connectors.iter_mut().zip(protonode_paths.iter_mut()).enumerate().rev() {
let callers = calling_protonodes.get(&protonode_index).unwrap();
let &(min_protonode_index, input_index) = callers.iter().min().unwrap();
let protonode_id = protonetwork[min_protonode_index].stable_node_id;
if let Some(value_connector) = value_connector.take() {
value_connector_callers.push((value_connector, (protonode_id, input_index)));
}
if let Some(protonode_path) = protonode_path.take() {
protonode_callers.push((protonode_path, (protonode_id, input_index)));
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!(),
}
}
}
}
let mut existing_ids = HashSet::new();
// Value nodes can be deduplicated if they share the same hash, since they do not depend on the input
let protonetwork = protonetwork
.into_iter()
.filter(|protonode| !(matches!(protonode.construction_args, ConstructionArgs::Value(_)) && !existing_ids.insert(protonode.stable_node_id)))
.collect();
Ok((protonetwork, value_connector_callers, protonode_callers))
log::debug!("protonetwork: {:?}", protonetwork);
Ok((ProtoNetwork::from_vec(protonetwork), value_connector_callers, protonode_callers))
}
fn get_input_from_absolute_connector(&mut self, traversal_input: &AbsoluteInputConnector) -> Option<&mut NodeInput> {
@ -632,39 +709,32 @@ impl NodeNetwork {
}
}
}
// Performs a recursive graph traversal starting from all protonode inputs and the root export until reaching the next protonode or value input.
// Automatically inserts value nodes by moving the value from the current network
// Performs a recursive graph traversal starting from the root export across all node inputs
// Inserts values into the protonetwork by moving the value from the current network
//
// protonetwork - The topologically sorted flattened protonetwork. The caller of each protonode is at a lower index. The output of the network is the first protonode
//
// calling protonodes - anytime a protonode is reached, the caller is added as a value with (caller protonetwork index, caller input index).
// This is necessary so the calling protonodes input can be looked up and mapped when generating SNI's
// None indicates that the caller is the traversal start, which is skipped
//
// Protonode indices - mapping of protonode path to its index in the protonetwork, updated when inserting a protonode
//
// Traversal input - current connector to traverse over. added to downstream_calling_inputs every time the function is called.
//
// downstream_calling_inputs - tracks all inputs reached during traversal
//
// any_input_to_downstream_protonode_input - used by the runtime/javascript to get the calling protonode input from any input connector.
// When a protonode is reached, each input connector in downstream_calling_inputs, is looked up in `any_input_to_downstream_protonode_input`. If there is an entry,
// Then the paths are compared, and the greater one is chosen using stable ordering.
// This is to ensure a constant mapping, since an export for instance can have multiple calling nodes in the parent network
//
// any_input_to_upstream_protonode - used by the runtime to get the node to evaluate for any given input connector.
// Each input connector is inserted into any_input_to_upstream_protonode with the value being the path to the reached protonode.
// It doesnt matter if its overwritten since it must have previously pointed to the same protonode anyways
//
pub fn traverse_input(
&mut self,
protonetwork: &mut Vec<ProtoNode>, // Flattened node id to protonode, stable node ids can only be generated once the network is fully flattened, since it runs in reverse
value_connector: &mut Vec<Option<AbsoluteInputConnector>>,
protonode_path: &mut Vec<Option<ProtonodePath>>,
calling_protonodes: &mut HashMap<usize, Vec<(usize, usize)>>, // A mapping of protonode path to all (flattened network indices, their input index) that called the protonode, used during SNI generation to remap inputs
protonode_indices: &mut HashMap<Vec<SNI>, usize>, // Mapping of protonode path to its index in the flattened protonetwork
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
protonode_indices: &mut HashMap<ProtonodePath, usize>,
// The original location of the current traversal
traversal_input: AbsoluteInputConnector,
// Protonode index, input index
traversal_start: (usize, usize),
// The protnode input which started the traversal. None if it is called from the root export
traversal_start: Option<(ProtonodePath, usize)>,
) {
let network_path = &traversal_input.network_path;
@ -730,90 +800,111 @@ impl NodeNetwork {
network_path: upstream_node_path.clone(),
connector: InputConnector::Export(output_index),
};
self.traverse_input(protonetwork, value_connector, protonode_path, calling_protonodes, protonode_indices, traversal_input, traversal_start);
self.traverse_input(protonetwork, value_protonode_indices, protonode_indices, traversal_input, traversal_start);
}
DocumentNodeImplementation::ProtoNode(protonode_id) => {
// Only insert the protonode if it has not previously been inserted
// Do not insert the protonode into the proto network or traverse over inputs if its already visited
let reached_protonode_index = match protonode_indices.get(&upstream_node_path) {
// The protonode has already been inserted, return its index
Some(reached_protonode_index) => *reached_protonode_index,
// Insert the protonode and traverse over inputs
None => {
let construction_args = ConstructionArgs::Nodes(NodeConstructionArgs {
identifier: protonode_id.clone(),
inputs: vec![NodeId(0); upstream_document_node.inputs.len()],
});
let protonode = ProtoNode {
construction_args,
// All protonodes take Context by default
input: concrete!(Context),
original_location: OriginalLocation {
protonode_path: upstream_node_path.clone().into(),
send_types_to_editor: true,
},
stable_node_id: NodeId(0),
// Check if the protonode has already been reached
let reached_protonode = match protonode_indices.get(&upstream_node_path) {
// The protonode has already been inserted, add the caller and node path to its metadata
Some(previous_protonode_index) => {
let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[*previous_protonode_index] else {
panic!("Previously inserted protonode must exist at mapped protonode index");
};
let new_protonode_index = protonetwork.len();
protonode_indices.insert(upstream_node_path.clone(), new_protonode_index);
protonetwork.push(protonode);
value_connector.push(None);
protonode_path.push(Some(upstream_node_path.into_boxed_slice()));
// Iterate over all upstream inputs, which will map the inputs to the index of the connected protonode
protonode
}
// Construct the protonode and traverse over inputs
None => {
let number_of_inputs = upstream_document_node.inputs.len();
let identifier = protonode_id.clone();
for input_index in 0..upstream_document_node.inputs.len() {
self.traverse_input(
protonetwork,
value_connector,
protonode_path,
calling_protonodes,
value_protonode_indices,
protonode_indices,
AbsoluteInputConnector {
network_path: network_path.clone(),
connector: InputConnector::node(upstream_node_id, input_index),
},
(new_protonode_index, input_index),
Some((upstream_node_path.clone(), input_index)),
);
}
new_protonode_index
let context_dependencies = NODE_CONTEXT_DEPENDENCY.lock().unwrap().get(identifier.name.as_ref()).cloned().unwrap_or_default();
let construction_args = ConstructionArgs::Nodes(NodeConstructionArgs {
identifier,
inputs: vec![None; number_of_inputs],
context_dependencies,
node_paths: Vec::new(),
});
let protonode = ProtoNode {
construction_args,
// All protonodes take Context by default
input: concrete!(Context),
stable_node_id: NodeId(0),
callers: Vec::new(),
caller: None,
};
let new_protonode_index = protonetwork.len();
protonetwork.push(ProtonodeEntry::Protonode(protonode));
protonode_indices.insert(upstream_node_path.clone(), new_protonode_index);
let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else {
panic!("Inserted protonode must exist at new_protonode_index");
};
protonode
}
};
calling_protonodes.entry(reached_protonode_index).or_insert_with(Vec::new).push(traversal_start);
// 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::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!(),
}
}
NodeInput::Value { tagged_value, .. } => {
// Deduplication of value nodes based on their tagged value, since they do not depend on the Context
//
use std::hash::Hasher;
let mut hasher = rustc_hash::FxHasher::default();
tagged_value.hash(&mut hasher);
let value_node_path = vec![NodeId(hasher.finish())];
// Only insert the value protonode if it has not previously been inserted
let value_protonode_index = match protonode_indices.get(&value_node_path) {
// The value input has already been inserted, return it the existing value nodes index
Some(value_protonode_index) => *value_protonode_index,
// Check if the protonode has already been reached
let reached_protonode = match value_protonode_indices.get(&traversal_input) {
// The protonode has already been inserted, add the caller and node path to its metadata
Some(previous_protonode_index) => {
let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[*previous_protonode_index] else {
panic!("Previously inserted protonode must exist at mapped protonode index");
};
protonode
}
// Insert the protonode and traverse over inputs
None => {
let protonode = ProtoNode {
construction_args: ConstructionArgs::Value(std::mem::replace(tagged_value, TaggedValue::None.into())),
let value_protonode = ProtoNode {
construction_args: ConstructionArgs::Value(NodeValueArgs {
value: std::mem::replace(tagged_value, TaggedValue::None.into()),
connector_paths: Vec::new(),
}),
input: concrete!(Context), // Could be ()
original_location: OriginalLocation {
protonode_path: Vec::new().into(),
send_types_to_editor: false,
},
stable_node_id: NodeId(0),
callers: Vec::new(),
caller: None,
};
let new_protonode_index = protonetwork.len();
protonode_indices.insert(value_node_path.clone(), new_protonode_index);
protonetwork.push(protonode);
value_connector.push(Some(traversal_input));
protonode_path.push(None);
new_protonode_index
protonetwork.push(ProtonodeEntry::Protonode(value_protonode));
value_protonode_indices.insert(traversal_input.clone(), new_protonode_index);
let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else {
panic!("Previously inserted protonode must exist at mapped protonode index");
};
protonode
}
};
calling_protonodes.entry(value_protonode_index).or_insert_with(Vec::new).push(traversal_start);
// 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, .. } => {
@ -823,35 +914,14 @@ impl NodeNetwork {
network_path: encapsulating_network_path,
connector: InputConnector::node(node_id, *import_index),
};
self.traverse_input(protonetwork, value_connector, protonode_path, calling_protonodes, protonode_indices, traversal_input, traversal_start);
self.traverse_input(protonetwork, value_protonode_indices, protonode_indices, traversal_input, traversal_start);
}
NodeInput::Scope(_cow) => unreachable!(),
NodeInput::Reflection(_document_node_metadata) => unreachable!(),
NodeInput::Inline(_inline_rust) => todo!(),
NodeInput::Scope(_) => unreachable!(),
NodeInput::Reflection(_) => unreachable!(),
NodeInput::Inline(_) => todo!(),
}
}
// pub fn collect_downstream_metadata(
// reached_protonode_index: usize,
// calling_protonodes: &mut HashMap<usize, Vec<(usize, usize)>>,
// protonode_indices: &mut HashMap<Vec<SNI>, usize>,
// downstream_calling_inputs: Vec<AbsoluteInputConnector>,
// ) {
// // Map the first downstream calling node input (which is traversed for every node input) to the reached protonode
// let downstream_protonode_caller = downstream_calling_inputs[0].clone();
// match &downstream_protonode_caller.connector {
// InputConnector::Node { node_id, input_index } => {
// // The calling protonode has already been added to the flattened network, so it can be looked up by index and the reached node can be mapped to it
// let mut calling_protonode_path = downstream_protonode_caller.network_path.clone();
// calling_protonode_path.push(*node_id);
// let calling_protonode_index = protonode_indices[&calling_protonode_path];
// }
// InputConnector::Export(_) => {}
// }
// }
/// Converts the `DocumentNode`s with a `DocumentNodeImplementation::Extract` into a `ClonedNode` that returns
/// the `DocumentNode` specified by the single `NodeInput::Node`.
/// The referenced node is removed from the network, and any `NodeInput::Node`s used by the referenced node are replaced with a generically typed network input.
@ -898,11 +968,17 @@ impl NodeNetwork {
}
#[derive(Debug)]
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),
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CompilationMetadata {
// Stored for every value input in the compiled network
pub protonode_callers_for_value: Vec<(AbsoluteInputConnector, CompiledProtonodeInput)>,
pub protonode_caller_for_values: Vec<(Vec<AbsoluteInputConnector>, CompiledProtonodeInput)>,
// Stored for every protonode in the compiled network
pub protonode_callers_for_node: Vec<(ProtonodePath, CompiledProtonodeInput)>,
pub protonode_caller_for_nodes: Vec<(Vec<ProtonodePath>, CompiledProtonodeInput)>,
pub types_to_add: Vec<(SNI, Vec<Type>)>,
pub types_to_remove: Vec<(SNI, usize)>,
}
@ -970,7 +1046,7 @@ pub struct AbsoluteOutputConnector {
}
/// Represents an output connector
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum OutputConnector {
#[serde(rename = "node")]
Node {

View file

@ -1,18 +1,18 @@
use super::DocumentNode;
use crate::proto::{Any as DAny, FutureAny};
use crate::wasm_application_io::WasmEditorApi;
use crate::wasm_application_io::WasmApplicationIoValue;
use dyn_any::DynAny;
pub use dyn_any::StaticType;
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
use graphene_application_io::SurfaceFrame;
use graphene_brush::brush_cache::BrushCache;
use graphene_brush::brush_stroke::BrushStroke;
use graphene_core::raster_types::CPU;
use graphene_core::raster_types::{CPU, GPU};
use graphene_core::transform::ReferencePoint;
use graphene_core::uuid::NodeId;
use graphene_core::vector::style::Fill;
use graphene_core::{Color, MemoHash, Node, Type};
use graphene_svg_renderer::RenderMetadata;
use graphene_svg_renderer::{GraphicElementRendered, RenderMetadata};
use std::fmt::Display;
use std::hash::Hash;
use std::marker::PhantomData;
@ -32,10 +32,9 @@ macro_rules! tagged_value {
$( $(#[$meta] ) *$identifier( $ty ), )*
RenderOutput(RenderOutput),
SurfaceFrame(SurfaceFrame),
#[serde(skip)]
EditorApi(Arc<WasmEditorApi>)
}
// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)
#[allow(clippy::derived_hash_with_manual_eq)]
impl Hash for TaggedValue {
@ -46,7 +45,6 @@ macro_rules! tagged_value {
$( Self::$identifier(x) => {x.hash(state)}),*
Self::RenderOutput(x) => x.hash(state),
Self::SurfaceFrame(x) => x.hash(state),
Self::EditorApi(x) => x.hash(state),
}
}
}
@ -58,7 +56,6 @@ macro_rules! tagged_value {
$( Self::$identifier(x) => Box::new(x), )*
Self::RenderOutput(x) => Box::new(x),
Self::SurfaceFrame(x) => Box::new(x),
Self::EditorApi(x) => Box::new(x),
}
}
/// Converts to a Arc<dyn Any + Send + Sync + 'static>
@ -68,7 +65,6 @@ macro_rules! tagged_value {
$( Self::$identifier(x) => Arc::new(x), )*
Self::RenderOutput(x) => Arc::new(x),
Self::SurfaceFrame(x) => Arc::new(x),
Self::EditorApi(x) => Arc::new(x),
}
}
/// Creates a graphene_core::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value
@ -78,7 +74,6 @@ macro_rules! tagged_value {
$( Self::$identifier(_) => concrete!($ty), )*
Self::RenderOutput(_) => concrete!(RenderOutput),
Self::SurfaceFrame(_) => concrete!(SurfaceFrame),
Self::EditorApi(_) => concrete!(&WasmEditorApi)
}
}
/// Attempts to downcast the dynamic type to a tagged value
@ -115,7 +110,6 @@ macro_rules! tagged_value {
$(TaggedValue::$identifier(value) => {any.downcast_ref::<$ty>().map_or(false, |v| v==value)}, )*
TaggedValue::RenderOutput(value) => any.downcast_ref::<RenderOutput>().map_or(false, |v| v==value),
TaggedValue::SurfaceFrame(value) => any.downcast_ref::<SurfaceFrame>().map_or(false, |v| v==value),
TaggedValue::EditorApi(value) => any.downcast_ref::<Arc<WasmEditorApi>>().map_or(false, |v| v==value),
}
}
pub fn from_type(input: &Type) -> Option<Self> {
@ -258,6 +252,9 @@ tagged_value! {
ReferencePoint(graphene_core::transform::ReferencePoint),
CentroidType(graphene_core::vector::misc::CentroidType),
BooleanOperation(graphene_path_bool::BooleanOperation),
EditorMetadata(EditorMetadata),
#[serde(skip)]
ApplicationIo(Arc<WasmApplicationIoValue>),
}
impl TaggedValue {
@ -382,18 +379,6 @@ impl TaggedValue {
_ => panic!("Passed value is not of type u32"),
}
}
pub fn as_renderable<'a>(value: &'a TaggedValue) -> Option<&'a dyn graphene_svg_renderer::GraphicElementRendered> {
match value {
TaggedValue::VectorData(v) => Some(v),
TaggedValue::RasterData(r) => Some(r),
TaggedValue::GraphicElement(e) => Some(e),
TaggedValue::GraphicGroup(g) => Some(g),
TaggedValue::ArtboardGroup(a) => Some(a),
TaggedValue::Artboard(a) => Some(a),
_ => None,
}
}
}
impl Display for TaggedValue {
@ -441,6 +426,8 @@ impl<T: AsRef<U> + Sync + Send, U: Sync + Send> UpcastAsRefNode<T, U> {
}
}
#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
pub struct RenderOutput {
pub data: RenderOutputType,
@ -460,6 +447,47 @@ impl Hash for RenderOutput {
}
}
impl Default for RenderOutput {
fn default() -> Self {
RenderOutput {
data: RenderOutputType::Image(Vec::new()),
metadata: RenderMetadata::default(),
}
}
}
// Passed as a scope input
#[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct EditorMetadata {
// pub imaginate_hostname: String,
pub use_vello: bool,
pub hide_artboards: bool,
// If exporting, hide the artboard name and do not collect metadata
pub for_export: bool,
pub view_mode: graphene_core::vector::style::ViewMode,
pub transform_to_viewport: bool,
}
unsafe impl dyn_any::StaticType for EditorMetadata {
type Static = EditorMetadata;
}
impl Default for EditorMetadata {
fn default() -> Self {
Self {
// imaginate_hostname: "http://localhost:7860/".into(),
#[cfg(target_arch = "wasm32")]
use_vello: false,
#[cfg(not(target_arch = "wasm32"))]
use_vello: true,
hide_artboards: false,
for_export: false,
view_mode: graphene_core::vector::style::ViewMode::Normal,
transform_to_viewport: true,
}
}
}
/// We hash the floats and so-forth despite it not being reproducible because all inputs to the node graph must be hashed otherwise the graph execution breaks (so sorry about this hack)
trait FakeHash {
fn hash<H: core::hash::Hasher>(&self, state: &mut H);
@ -509,3 +537,47 @@ mod fake_hash {
}
}
}
macro_rules! thumbnail_render {
( $( $ty:ty ),* $(,)? ) => {
pub fn render_thumbnail_if_change(new_value: &Arc<dyn std::any::Any + Send + Sync>, old_value: Option<&Arc<dyn std::any::Any + Send + Sync>>) -> ThumbnailRenderResult {
$(
if let Some(new_value) = new_value.downcast_ref::<$ty>() {
match old_value {
None => return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail()),
Some(old_value) => {
if let Some(old_value) = old_value.downcast_ref::<$ty>() {
match new_value == old_value {
true => return ThumbnailRenderResult::NoChange,
false => return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail())
}
} else {
return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail())
}
},
}
}
)*
return ThumbnailRenderResult::ClearThumbnail;
}
};
}
thumbnail_render! {
graphene_core::GraphicGroupTable,
graphene_core::vector::VectorDataTable,
graphene_core::Artboard,
graphene_core::ArtboardGroupTable,
graphene_core::raster_types::RasterDataTable<CPU>,
graphene_core::raster_types::RasterDataTable<GPU>,
graphene_core::GraphicElement,
Option<Color>,
Vec<Color>,
}
pub enum ThumbnailRenderResult {
NoChange,
// Cleared if there is an error or the data could not be rendered
ClearThumbnail,
UpdateThumbnail(String),
}

View file

@ -1,4 +1,4 @@
use crate::document::{InlineRust, value};
use crate::document::{AbsoluteInputConnector, InlineRust, ProtonodeEntry, value};
pub use graphene_core::registry::*;
use graphene_core::uuid::{NodeId, ProtonodePath, SNI};
use graphene_core::*;
@ -6,18 +6,43 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::ops::Deref;
// #[derive(Debug, Default, PartialEq, Clone, Hash, Eq, serde::Serialize, serde::Deserialize)]
// /// A list of [`ProtoNode`]s, which is an intermediate step between the [`crate::document::NodeNetwork`] and the `BorrowTree` containing a single flattened network.
// pub struct ProtoNetwork {
// // TODO: remove this since it seems to be unused?
// // Should a proto Network even allow inputs? Don't think so
// pub inputs: Vec<NodeId>,
// /// The node ID that provides the output. This node is then responsible for calling the rest of the graph.
// pub output: NodeId,
// /// A list of nodes stored in a Vec to allow for sorting.
// pub nodes: Vec<(NodeId, ProtoNode)>,
// }
#[derive(Debug, Default)]
/// A list of [`ProtoNode`]s, which is an intermediate step between the [`crate::document::NodeNetwork`] and the `BorrowTree` containing a single flattened network.
pub struct ProtoNetwork {
/// A list of nodes stored in a Vec to allow for sorting.
nodes: Vec<ProtonodeEntry>,
/// The most downstream node in the protonetwork
pub output: NodeId,
}
impl ProtoNetwork {
pub fn from_vec(nodes: Vec<ProtonodeEntry>) -> Self {
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
}
};
ProtoNetwork { nodes, output }
}
pub fn nodes(&self) -> impl Iterator<Item = &ProtoNode> {
self.nodes
.iter()
.filter_map(|entry| if let ProtonodeEntry::Protonode(protonode) = entry { Some(protonode) } else { None })
}
pub fn into_nodes(self) -> impl Iterator<Item = ProtoNode> {
self.nodes
.into_iter()
.filter_map(|entry| if let ProtonodeEntry::Protonode(protonode) = entry { Some(protonode) } else { None })
}
}
// impl core::fmt::Display for ProtoNetwork {
// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
@ -69,59 +94,57 @@ use std::hash::Hash;
// }
// }
#[derive(Debug, Clone, PartialEq, Hash, Eq)]
#[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>>,
}
#[derive(Debug, Clone)]
pub struct NodeConstructionArgs {
// Used to get the constructor from the function in `node_registry.rs`.
pub identifier: ProtoNodeIdentifier,
/// A list of stable node ids used as inputs to the constructor
pub inputs: Vec<SNI>,
// A node is dependent on whatever is marked in its implementation, as well as all inputs
// If a node is dependent on more than its input, then a context nullification node is placed on the input
// 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, PartialEq)]
#[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>,
}
#[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 {
/// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe)
Value(MemoHash<value::TaggedValue>),
Value(NodeValueArgs),
Nodes(NodeConstructionArgs),
/// Used for GPU computation to work around the limitations of rust-gpu.
Inline(InlineRust),
}
impl Eq for ConstructionArgs {}
impl Hash for ConstructionArgs {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
Self::Nodes(nodes) => {
for node in &nodes.inputs {
node.hash(state);
}
}
Self::Value(value) => value.hash(state),
Self::Inline(inline) => inline.hash(state),
}
}
}
impl ConstructionArgs {
// TODO: what? Used in the gpu_compiler crate for something.
pub fn new_function_args(&self) -> Vec<String> {
match self {
ConstructionArgs::Nodes(nodes) => nodes.inputs.iter().map(|n| format!("n{:0x}", n.0)).collect(),
ConstructionArgs::Value(value) => vec![value.to_primitive_string()],
ConstructionArgs::Inline(inline) => vec![inline.expr.clone()],
}
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct OriginalLocation {
/// The original location to the document node - e.g. [grandparent_id, parent_id, node_id].
pub protonode_path: ProtonodePath,
// // Types should not be sent for autogenerated nodes or value nodes, which are not visible and inserted during compilation
pub send_types_to_editor: bool,
}
// impl ConstructionArgs {
// // TODO: what? Used in the gpu_compiler crate for something.
// pub fn new_function_args(&self) -> Vec<String> {
// match self {
// ConstructionArgs::Nodes(nodes) => nodes.inputs.iter().map(|n| format!("n{:0x}", n.0)).collect(),
// ConstructionArgs::Value(value) => vec![value.to_primitive_string()],
// ConstructionArgs::Inline(inline) => vec![inline.expr.clone()],
// }
// }
// }
#[derive(Debug, Clone)]
/// A proto node is an intermediate step between the `DocumentNode` and the boxed struct that actually runs the node (found in the [`BorrowTree`]).
@ -130,43 +153,61 @@ pub struct OriginalLocation {
pub struct ProtoNode {
pub construction_args: ConstructionArgs,
pub input: Type,
pub original_location: OriginalLocation,
pub stable_node_id: SNI,
// Each protonode stores the path and input index of the protonodes which called it
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)>,
}
impl Default for ProtoNode {
fn default() -> Self {
Self {
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()),
construction_args: ConstructionArgs::Value(NodeValueArgs {
value: value::TaggedValue::U32(0).into(),
connector_paths: Vec::new(),
}),
input: concrete!(Context),
original_location: Default::default(),
stable_node_id: NodeId(0),
callers: Vec::new(),
caller: None,
}
}
}
impl ProtoNode {
/// Construct a new [`ProtoNode`] with the specified construction args and a `ClonedNode` implementation.
pub fn value(value: ConstructionArgs, path: Vec<NodeId>, stable_node_id: SNI) -> Self {
let inputs_exposed = match &value {
ConstructionArgs::Nodes(nodes) => nodes.inputs.len() + 1,
_ => 2,
};
pub fn value(value: ConstructionArgs, stable_node_id: SNI) -> Self {
Self {
construction_args: value,
input: concrete!(Context),
original_location: OriginalLocation {
protonode_path: path.into(),
send_types_to_editor: false,
},
stable_node_id,
callers: Vec::new(),
caller: None,
}
}
// Hashes the inputs and implementation of non value nodes, and the value for value nodes
pub fn generate_stable_node_id(&mut self) {
use std::hash::Hasher;
let mut hasher = rustc_hash::FxHasher::default();
match &self.construction_args {
ConstructionArgs::Nodes(nodes) => {
for upstream_input in &nodes.inputs {
upstream_input.as_ref().unwrap().input_sni.hash(&mut hasher);
}
nodes.identifier.hash(&mut hasher);
}
ConstructionArgs::Value(value) => value.value.hash(&mut hasher),
ConstructionArgs::Inline(_) => todo!(),
}
self.stable_node_id = NodeId(hasher.finish());
}
}
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum GraphErrorType {
NodeNotFound(NodeId),
InputNodeNotFound(NodeId),
UnexpectedGenerics { index: usize, inputs: Vec<Type> },
NoImplementations,
@ -178,7 +219,6 @@ impl Debug for GraphErrorType {
// TODO: format with the document graph context so the input index is the same as in the graph UI.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GraphErrorType::NodeNotFound(id) => write!(f, "Input node {id} is not present in the typing context"),
GraphErrorType::InputNodeNotFound(id) => write!(f, "Input node {id} is not present in the typing context"),
GraphErrorType::UnexpectedGenerics { index, inputs } => write!(f, "Generic inputs should not exist but found at {index}: {inputs:?}"),
GraphErrorType::NoImplementations => write!(f, "No implementations found"),
@ -222,7 +262,7 @@ impl Debug for GraphErrorType {
}
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct GraphError {
pub node_path: Vec<NodeId>,
pub stable_node_id: SNI,
pub identifier: Cow<'static, str>,
pub error: GraphErrorType,
}
@ -231,11 +271,11 @@ impl GraphError {
let identifier = match &node.construction_args {
ConstructionArgs::Nodes(node_construction_args) => node_construction_args.identifier.name.clone(),
// Values are inserted into upcast nodes
ConstructionArgs::Value(memo_hash) => "Value Node".into(),
ConstructionArgs::Inline(inline_rust) => "Inline".into(),
ConstructionArgs::Value(node_value_args) => format!("{:?} Value Node", node_value_args.value.deref().ty()).into(),
ConstructionArgs::Inline(_) => "Inline".into(),
};
Self {
node_path: node.original_location.protonode_path.to_vec(),
stable_node_id: node.stable_node_id,
identifier,
error: text.into(),
}
@ -243,11 +283,7 @@ impl GraphError {
}
impl Debug for GraphError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodeGraphError")
.field("path", &self.node_path.iter().map(|id| id.0).collect::<Vec<_>>())
.field("identifier", &self.identifier.to_string())
.field("error", &self.error)
.finish()
f.debug_struct("NodeGraphError").field("identifier", &self.identifier.to_string()).field("error", &self.error).finish()
}
}
pub type GraphErrors = Vec<GraphError>;
@ -256,17 +292,17 @@ pub type GraphErrors = Vec<GraphError>;
#[derive(Default, Clone, dyn_any::DynAny)]
pub struct TypingContext {
lookup: Cow<'static, HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>>,
monitor_lookup: Cow<'static, HashMap<Type, MonitorConstructor>>,
cache_lookup: Cow<'static, HashMap<Type, CacheConstructor>>,
inferred: HashMap<NodeId, NodeIOTypes>,
constructor: HashMap<NodeId, NodeConstructor>,
}
impl TypingContext {
/// Creates a new `TypingContext` with the given lookup table.
pub fn new(lookup: &'static HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>, monitor_lookup: &'static HashMap<Type, MonitorConstructor>) -> Self {
pub fn new(lookup: &'static HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>, cache_lookup: &'static HashMap<Type, CacheConstructor>) -> Self {
Self {
lookup: Cow::Borrowed(lookup),
monitor_lookup: Cow::Borrowed(monitor_lookup),
cache_lookup: Cow::Borrowed(cache_lookup),
..Default::default()
}
}
@ -274,9 +310,9 @@ impl TypingContext {
/// Updates the `TypingContext` with a given proto network. This will infer the types of the nodes
/// and store them in the `inferred` field. The proto network has to be topologically sorted
/// and contain fully resolved stable node ids.
pub fn update(&mut self, network: &Vec<ProtoNode>) -> Result<(), GraphErrors> {
pub fn update(&mut self, network: &ProtoNetwork) -> Result<(), GraphErrors> {
// Update types from the most upstream nodes first
for node in network.iter().rev() {
for node in network.nodes() {
self.infer(node.stable_node_id, node)?;
}
Ok(())
@ -292,9 +328,9 @@ impl TypingContext {
self.constructor.get(&node_id).copied()
}
// Returns the monitor node constructor for a given type {
pub fn monitor_constructor(&self, monitor_type: &Type) -> Option<MonitorConstructor> {
self.monitor_lookup.get(monitor_type).copied()
// Returns the cache node constructor for a given type {
pub fn cache_constructor(&self, cache_type: &Type) -> Option<CacheConstructor> {
self.cache_lookup.get(cache_type).copied()
}
/// Returns the type of a given node id if it exists
@ -314,7 +350,7 @@ 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.ty())), vec![]);
let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.value.ty())), vec![]);
self.inferred.insert(node_id, types.clone());
return Ok(types);
}
@ -323,10 +359,11 @@ impl TypingContext {
let inputs = construction_args
.inputs
.iter()
.map(|id| id.as_ref().unwrap().input_sni)
.map(|id| {
self.inferred
.get(id)
.ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NodeNotFound(*id))])
.get(&id)
.ok_or_else(|| vec![GraphError::new(node, GraphErrorType::InputNodeNotFound(id))])
.map(|node| node.ty())
})
.collect::<Result<Vec<Type>, GraphErrors>>()?;

View file

@ -1,8 +1,9 @@
use dyn_any::StaticType;
use graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId};
use graphene_application_io::{ApplicationError, ApplicationIo, ApplicationIoValue, ResourceFuture, SurfaceHandle, SurfaceId};
#[cfg(target_arch = "wasm32")]
use js_sys::{Object, Reflect};
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::Arc;
#[cfg(target_arch = "wasm32")]
use std::sync::atomic::AtomicU64;
@ -56,6 +57,8 @@ unsafe impl Sync for WindowWrapper {}
#[cfg(target_arch = "wasm32")]
unsafe impl Send for WindowWrapper {}
pub type WasmApplicationIoValue = ApplicationIoValue<WasmApplicationIo>;
#[derive(Debug, Default)]
pub struct WasmApplicationIo {
#[cfg(target_arch = "wasm32")]
@ -156,20 +159,12 @@ unsafe impl StaticType for WasmApplicationIo {
type Static = WasmApplicationIo;
}
impl<'a> From<&'a WasmEditorApi> for &'a WasmApplicationIo {
fn from(editor_api: &'a WasmEditorApi) -> Self {
editor_api.application_io.as_ref().unwrap()
}
}
#[cfg(feature = "wgpu")]
impl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor {
fn from(app_io: &'a WasmApplicationIo) -> Self {
app_io.gpu_executor.as_ref().unwrap()
}
}
pub type WasmEditorApi = graphene_application_io::EditorApi<WasmApplicationIo>;
impl ApplicationIo for WasmApplicationIo {
#[cfg(target_arch = "wasm32")]
type Surface = HtmlCanvasElement;

View file

@ -1,14 +1,15 @@
use clap::{Args, Parser, Subcommand};
use fern::colors::{Color, ColoredLevelConfig};
use futures::executor::block_on;
use graph_craft::document::value::EditorMetadata;
use graph_craft::document::*;
use graph_craft::graphene_compiler::{Compiler, Executor};
use graph_craft::proto::{ProtoNetwork, ProtoNode};
use graph_craft::util::load_network;
use graph_craft::wasm_application_io::EditorPreferences;
use graph_craft::wasm_application_io::{EditorPreferences, WasmApplicationIoValue};
use graphene_core::text::FontCache;
use graphene_std::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use graphene_std::application_io::{ApplicationIo, ApplicationIoValue, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_std::wasm_application_io::WasmApplicationIo;
use interpreted_executor::dynamic_executor::DynamicExecutor;
use interpreted_executor::util::wrap_network_in_scope;
use std::error::Error;
@ -92,14 +93,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
use_vello: true,
..Default::default()
};
let editor_api = Arc::new(WasmEditorApi {
font_cache: FontCache::default(),
application_io: Some(application_io.into()),
node_graph_message_sender: Box::new(UpdateLogger {}),
editor_preferences: Box::new(preferences),
});
let application_io = Arc::new(ApplicationIoValue(Some(Arc::new(application_io))));
let proto_graph = compile_graph(document_string, editor_api)?;
let proto_graph = compile_graph(document_string, application_io)?;
match app.command {
Command::Compile { print_proto, .. } => {
@ -180,17 +176,16 @@ fn fix_nodes(network: &mut NodeNetwork) {
}
}
}
fn compile_graph(document_string: String, editor_api: Arc<WasmEditorApi>) -> Result<Vec<ProtoNode>, Box<dyn Error>> {
fn compile_graph(document_string: String, application_io: Arc<WasmApplicationIoValue>) -> Result<ProtoNetwork, Box<dyn Error>> {
let mut network = load_network(&document_string);
fix_nodes(&mut network);
let substitutions = preprocessor::generate_node_substitutions();
let substitutions: std::collections::HashMap<String, DocumentNode> = preprocessor::generate_node_substitutions();
preprocessor::expand_network(&mut network, &substitutions);
let mut wrapped_network = wrap_network_in_scope(network.clone(), editor_api);
let mut wrapped_network = wrap_network_in_scope(network, Arc::new(FontCache::default()), EditorMetadata::default(), application_io);
let compiler = Compiler {};
wrapped_network.flatten().map(|result|result.0).map_err(|x| x.into())
wrapped_network.flatten().map(|result| result.0).map_err(|x| x.into())
}
fn create_executor(proto_network: ProtoNetwork) -> Result<DynamicExecutor, Box<dyn Error>> {

View file

@ -1,9 +1,14 @@
use dyn_any::StaticType;
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::NodeIO;
use graphene_core::OwnedContextImpl;
use graphene_core::WasmNotSend;
pub use graphene_core::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode};
use graphene_core::transform::Footprint;
pub use graphene_core::{Node, generic, ops};
pub trait IntoTypeErasedNode<'n> {
@ -46,3 +51,115 @@ pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(),
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
DowncastBothNode::new(n)
}
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();
log::debug!("evaluating with context: {:?}", editor_context.to_context());
self.first.eval(Box::new(editor_context.to_context())).await
})
}
}
impl EditorContextToContext {
pub const fn new(first: SharedNodeContainer) -> Self {
EditorContextToContext { first }
}
}
#[derive(Debug, Clone, Default)]
pub struct EditorContext {
pub footprint: Option<Footprint>,
pub downstream_transform: Option<DAffine2>,
pub real_time: Option<f64>,
pub animation_time: Option<f64>,
pub index: Option<usize>,
// #[serde(skip)]
// pub editor_var_args: Option<(Vec<String>, Vec<Arc<Box<[dyn std::any::Any + 'static + std::panic::UnwindSafe]>>>)>,
}
unsafe impl StaticType for EditorContext {
type Static = EditorContext;
}
// impl Default for EditorContext {
// fn default() -> Self {
// EditorContext {
// footprint: None,
// downstream_transform: None,
// real_time: None,
// animation_time: None,
// index: None,
// // editor_var_args: None,
// }
// }
// }
impl EditorContext {
pub fn to_context(&self) -> Context {
let mut context = OwnedContextImpl::default();
if let Some(footprint) = self.footprint {
context.set_footprint(footprint);
}
if let Some(footprint) = self.footprint {
context.set_footprint(footprint);
}
// if let Some(downstream_transform) = self.downstream_transform {
// context.set_downstream_transform(downstream_transform);
// }
if let Some(real_time) = self.real_time {
context.set_real_time(real_time);
}
if let Some(animation_time) = self.animation_time {
context.set_animation_time(animation_time);
}
if let Some(index) = self.index {
context.set_index(index);
}
// if let Some(editor_var_args) = self.editor_var_args {
// let (variable_names, values)
// context.set_varargs((variable_names, values))
// }
context.into_context()
}
}
pub struct NullificationNode {
first: SharedNodeContainer,
nullify: Vec<ContextDependency>,
}
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) => {
log::debug!("Nullifying inputs: {:?}", self.nullify);
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 })
}
}
impl NullificationNode {
pub fn new(first: SharedNodeContainer, nullify: Vec<ContextDependency>) -> Self {
Self { first, nullify }
}
}

View file

@ -1,12 +1,11 @@
use crate::vector::VectorDataTable;
use graph_craft::wasm_application_io::WasmEditorApi;
use crate::vector::{VectorData, VectorDataTable};
use graphene_core::Ctx;
pub use graphene_core::text::*;
#[node_macro::node(category(""))]
fn text<'i: 'n>(
_: impl Ctx,
editor: &'i WasmEditorApi,
font_cache: std::sync::Arc<FontCache>,
text: String,
font_name: Font,
#[unit(" px")]
@ -41,7 +40,7 @@ fn text<'i: 'n>(
tilt,
};
let font_data = editor.font_cache.get(&font_name).map(|f| load_font(f));
let font_data = font_cache.get(&font_name).map(|f| load_font(f));
to_path(&text, font_data, typesetting, per_glyph_instances)
}

View file

@ -1,16 +1,16 @@
use graph_craft::document::value::RenderOutput;
pub use graph_craft::document::value::RenderOutputType;
use graph_craft::document::value::{EditorMetadata, RenderOutput};
pub use graph_craft::wasm_application_io::*;
use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig};
use graphene_application_io::ApplicationIo;
#[cfg(target_arch = "wasm32")]
use graphene_core::instances::Instances;
#[cfg(target_arch = "wasm32")]
use graphene_core::math::bbox::Bbox;
use graphene_core::raster::image::Image;
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
use graphene_core::raster_types::{CPU, GPU, Raster, RasterDataTable};
use graphene_core::transform::Footprint;
use graphene_core::vector::VectorDataTable;
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend};
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, WasmNotSend};
use graphene_svg_renderer::RenderMetadata;
use graphene_svg_renderer::{GraphicElementRendered, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};
@ -26,8 +26,8 @@ use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
#[cfg(feature = "wgpu")]
#[node_macro::node(category("Debug: GPU"))]
async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
Arc::new(editor.application_io.as_ref().unwrap().create_window())
async fn create_surface<'a: 'n>(_: impl Ctx, application_io: WasmApplicationIoValue) -> Arc<WasmSurfaceHandle> {
Arc::new(application_io.0.as_ref().unwrap().create_window())
}
// TODO: Fix and reenable in order to get the 'Draw Canvas' node working again.
@ -59,20 +59,20 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<W
// }
// }
#[node_macro::node(category("Web Request"))]
async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> {
let Some(api) = editor.application_io.as_ref() else {
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
};
let Ok(data) = api.load_resource(url) else {
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
};
let Ok(data) = data.await else {
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
};
// #[node_macro::node(category("Web Request"))]
// async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> {
// let Some(api) = editor.application_io.as_ref() else {
// return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
// };
// let Ok(data) = api.load_resource(url) else {
// return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
// };
// let Ok(data) = data.await else {
// return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
// };
data
}
// data
// }
#[node_macro::node(category("Web Request"))]
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> RasterDataTable<CPU> {
@ -118,16 +118,16 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p
#[cfg(feature = "vello")]
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
async fn render_canvas(
render_config: RenderConfig,
footprint: Footprint,
hide_artboards: bool,
data: impl GraphicElementRendered,
editor: &WasmEditorApi,
application_io: Arc<WasmApplicationIoValue>,
surface_handle: wgpu_executor::WgpuSurface,
render_params: RenderParams,
) -> RenderOutputType {
use graphene_application_io::SurfaceFrame;
let footprint = render_config.viewport;
let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() else {
let Some(exec) = application_io.0.as_ref().unwrap().gpu_executor() else {
unreachable!("Attempted to render with Vello when no GPU executor is available");
};
use vello::*;
@ -142,7 +142,7 @@ async fn render_canvas(
scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array())));
let mut background = Color::from_rgb8_srgb(0x22, 0x22, 0x22);
if !data.contains_artboard() && !render_config.hide_artboards {
if !data.contains_artboard() && !hide_artboards {
background = Color::WHITE;
}
exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y, &context, background)
@ -151,7 +151,7 @@ async fn render_canvas(
let frame = SurfaceFrame {
surface_id: surface_handle.window_id,
resolution: render_config.viewport.resolution,
resolution: footprint.resolution,
transform: glam::DAffine2::IDENTITY,
};
@ -230,73 +230,61 @@ where
#[node_macro::node(category(""))]
async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
render_config: RenderConfig,
editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>,
context: impl Ctx + ExtractFootprint,
editor_metadata: EditorMetadata,
application_io: Arc<WasmApplicationIoValue>,
#[implementations(
Context -> VectorDataTable,
Context -> RasterDataTable<CPU>,
Context -> GraphicGroupTable,
Context -> graphene_core::Artboard,
Context -> graphene_core::ArtboardGroupTable,
Context -> Option<Color>,
Context -> Vec<Color>,
Context -> bool,
Context -> f32,
Context -> f64,
Context -> String,
VectorDataTable,
RasterDataTable<CPU>,
RasterDataTable<GPU>,
GraphicGroupTable,
graphene_core::Artboard,
graphene_core::ArtboardGroupTable,
Option<Color>,
Vec<Color>,
bool,
f32,
f64,
String,
)]
data: impl Node<Context<'static>, Output = T>,
data: T,
_surface_handle: impl Node<Context<'static>, Output = Option<wgpu_executor::WgpuSurface>>,
) -> RenderOutput {
let footprint = render_config.viewport;
let ctx = OwnedContextImpl::default()
.with_footprint(footprint)
.with_real_time(render_config.time.time)
.with_animation_time(render_config.time.animation_time.as_secs_f64())
.into_context();
ctx.footprint();
let Some(footprint) = context.try_footprint().copied() else {
log::error!("Footprint must be Some when rendering");
return RenderOutput::default();
};
let RenderConfig { hide_artboards, for_export, .. } = render_config;
let render_params = RenderParams {
view_mode: render_config.view_mode,
view_mode: editor_metadata.view_mode,
culling_bounds: None,
thumbnail: false,
hide_artboards,
for_export,
hide_artboards: editor_metadata.hide_artboards,
for_export: editor_metadata.for_export,
for_mask: false,
alignment_parent_transform: None,
};
let data = data.eval(ctx.clone()).await;
let editor_api = editor_api.eval(None).await;
#[cfg(all(feature = "vello", not(test)))]
let surface_handle = _surface_handle.eval(None).await;
let use_vello = editor_api.editor_preferences.use_vello();
let use_vello = editor_metadata.use_vello;
#[cfg(all(feature = "vello", not(test)))]
let use_vello = use_vello && surface_handle.is_some();
let mut metadata = RenderMetadata::default();
data.collect_metadata(&mut metadata, footprint, None);
let output_format = render_config.export_format;
let data = match output_format {
ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint),
ExportFormat::Canvas => {
if use_vello && editor_api.application_io.as_ref().unwrap().gpu_executor().is_some() {
#[cfg(all(feature = "vello", not(test)))]
return RenderOutput {
data: render_canvas(render_config, data, editor_api, surface_handle.unwrap(), render_params).await,
metadata,
};
#[cfg(any(not(feature = "vello"), test))]
render_svg(data, SvgRender::new(), render_params, footprint)
} else {
render_svg(data, SvgRender::new(), render_params, footprint)
}
}
_ => todo!("Non-SVG render output for {output_format:?}"),
let data = if use_vello {
#[cfg(all(feature = "vello", not(test)))]
return RenderOutput {
data: render_canvas(footprint, editor_metadata.hide_artboards, data, application_io, surface_handle.unwrap(), render_params).await,
metadata,
};
#[cfg(any(not(feature = "vello"), test))]
render_svg(data, SvgRender::new(), render_params, footprint)
} else {
render_svg(data, SvgRender::new(), render_params, footprint)
};
RenderOutput { data, metadata }
}

View file

@ -211,6 +211,30 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity {
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams);
fn render_thumbnail(&self) -> String {
let bounds = self.bounding_box(DAffine2::IDENTITY, true);
let render_params = RenderParams {
view_mode: ViewMode::Normal,
culling_bounds: bounds,
thumbnail: true,
hide_artboards: false,
for_export: false,
for_mask: false,
alignment_parent_transform: None,
};
// Render the thumbnail data into an SVG string
let mut render = SvgRender::new();
self.render_svg(&mut render, &render_params);
// Give the SVG a viewbox and outer <svg>...</svg> wrapper tag
// let [min, max] = bounds.unwrap_or_default();
// render.format_svg(min, max);
render.svg.to_svg_string()
}
/// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection.
fn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}

View file

@ -7,7 +7,7 @@ use interpreted_executor::dynamic_executor::DynamicExecutor;
pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) {
let mut network = load_from_name(name);
let proto_network = network.flatten().unwrap();
let proto_network = network.flatten().unwrap().0;
let executor = block_on(DynamicExecutor::new(proto_network.0)).unwrap();
(executor, proto_network)
}

View file

@ -1,17 +1,17 @@
use crate::node_registry::{MONITOR_NODES, NODE_REGISTRY};
use crate::node_registry::{CACHE_NODES, NODE_REGISTRY};
use dyn_any::StaticType;
use glam::DAffine2;
use graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode};
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, downcast_node};
use graph_craft::document::ProtonodeEntry;
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::application_io::{ExportFormat, RenderConfig, TimingInformation};
use graphene_std::memo::{IntrospectMode, MonitorNode};
use graphene_std::transform::Footprint;
use graphene_std::any::{EditorContext, EditorContextToContext, NullificationNode};
use graphene_std::memo::IntrospectMode;
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI};
use graphene_std::{NodeIOTypes, OwnedContextImpl};
use graphene_std::{Context, MemoHash};
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::ptr::null;
use std::sync::Arc;
/// An executor of a node graph that does not require an online compilation server, and instead uses `Box<dyn ...>`.
@ -32,25 +32,24 @@ impl Default for DynamicExecutor {
Self {
output: None,
tree: Default::default(),
typing_context: TypingContext::new(&NODE_REGISTRY, &MONITOR_NODES),
typing_context: TypingContext::new(&NODE_REGISTRY, &CACHE_NODES),
}
}
}
impl DynamicExecutor {
pub async fn new(proto_network: Vec<ProtoNode>) -> Result<Self, GraphErrors> {
pub async fn new(proto_network: ProtoNetwork) -> Result<Self, GraphErrors> {
let mut typing_context = TypingContext::default();
typing_context.update(&proto_network)?;
let output = proto_network.get(0).map(|protonode| protonode.stable_node_id);
let output = Some(proto_network.output);
let tree = BorrowTree::new(proto_network, &typing_context).await?;
Ok(Self { tree, output, typing_context })
}
/// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible.
#[cfg_attr(debug_assertions, inline(never))]
pub async fn update(mut self, proto_network: Vec<ProtoNode>) -> Result<(Vec<(SNI, Vec<Type>)>, Vec<(SNI, usize)>), GraphErrors> {
self.output = proto_network.get(0).map(|protonode| protonode.stable_node_id);
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(Vec<(SNI, Vec<Type>)>, Vec<(SNI, usize)>), 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.
@ -58,12 +57,9 @@ impl DynamicExecutor {
let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?;
let mut remove = Vec::new();
for sni in orphaned_proto_nodes {
let Some(types) = self.typing_context.type_of(sni) else {
log::error!("Could not get type for protonode {sni} when removing");
continue;
};
remove.push((sni, types.inputs.len()));
self.tree.free_node(&sni, types.inputs.len());
if let Some(number_of_inputs) = self.tree.free_node(&sni) {
remove.push((sni, number_of_inputs));
}
self.typing_context.remove_inference(&sni);
}
@ -71,7 +67,6 @@ impl DynamicExecutor {
.into_iter()
.filter_map(|sni| {
let Some(types) = self.typing_context.type_of(sni) else {
log::debug!("Could not get type for added node: {sni}");
return None;
};
Some((sni, types.inputs.clone()))
@ -82,24 +77,27 @@ impl DynamicExecutor {
}
/// Intospect the value for that specific protonode input, returning for example the cached value for a monitor node.
pub fn introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) -> Result<Box<dyn std::any::Any + Send + Sync>, IntrospectError> {
let node = self.get_monitor_node_container(protonode_input)?;
node.introspect(introspect_mode).ok_or(IntrospectError::IntrospectNotImplemented)
pub fn 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_monitor_node_container(protonode_input) else {
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_monitor_node_container(&self, protonode_input: CompiledProtonodeInput) -> Result<SharedNodeContainer, IntrospectError> {
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 monitor_node_id = NodeId(protonode_input.0.0 + protonode_input.1 as u64 + 1);
let inserted_node = self.tree.nodes.get(&monitor_node_id).ok_or(IntrospectError::ProtoNodeNotFound(monitor_node_id))?;
Ok(inserted_node.clone())
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())
}
pub fn input_type(&self) -> Option<Type> {
@ -118,15 +116,38 @@ impl DynamicExecutor {
self.output.and_then(|output| self.typing_context.type_of(output).map(|node_io| node_io.return_value.clone()))
}
pub fn execute<I>(&self, input: I) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>>
// If node to evaluate is None then the most downstream node is used
pub async fn evaluate_from_node(&self, editor_context: EditorContext, node_to_evaluate: Option<SNI>) -> Result<TaggedValue, String> {
let node_to_evaluate: NodeId = node_to_evaluate
.or_else(|| self.output)
.ok_or("Could not find output node when evaluating network. Has the network been compiled?")?;
let input_type = self
.typing_context
.type_of(node_to_evaluate)
.map(|node_io| node_io.call_argument.clone())
.ok_or("Could not get input type of network to execute".to_string())?;
// 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()),
t if t == concrete!(()) => (&self).execute((), node_to_evaluate).await.map_err(|e| e.to_string()),
t => Err(format!("Invalid input type {t:?}")),
};
let result = match result {
Ok(value) => value,
Err(e) => return Err(e),
};
Ok(result)
}
pub fn execute<I>(&self, input: I, protonode_id: SNI) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>>
where
I: dyn_any::StaticType + 'static + Send + Sync + std::panic::UnwindSafe,
{
Box::pin(async move {
use futures::FutureExt;
let output_node = self.output.ok_or("Could not execute network before compilation")?;
let result = self.tree.eval_tagged_value(output_node, input);
let result = self.tree.eval_tagged_value(protonode_id, input);
let wrapped_result = std::panic::AssertUnwindSafe(result).catch_unwind().await;
match wrapped_result {
@ -138,95 +159,14 @@ impl DynamicExecutor {
}
})
}
// If node to evaluate is None then the most downstream node is used
// pub async fn evaluate_from_node(&self, editor_context: EditorContext, node_to_evaluate: Option<SNI>) -> Result<TaggedValue, String> {
// let node_to_evaluate: NodeId = node_to_evaluate
// .or_else(|| self.output)
// .ok_or("Could not find output node when evaluating network. Has the network been compiled?")?;
// let input_type = self
// .typing_context
// .type_of(node_to_evaluate)
// .map(|node_io| node_io.call_argument.clone())
// .ok_or("Could not get input type of network to execute".to_string())?;
// let result = match input_type {
// t if t == concrete!(EditorContext) => self.execute(editor_context, node_to_evaluate).await.map_err(|e| e.to_string()),
// t if t == concrete!(()) => (&self).execute((), node_to_evaluate).await.map_err(|e| e.to_string()),
// t => Err(format!("Invalid input type {t:?}")),
// };
// let result = match result {
// Ok(value) => value,
// Err(e) => return Err(e),
// };
// Ok(result)
// }
}
#[derive(Debug, Clone, Default)]
pub struct EditorContext {
// pub footprint: Option<Footprint>,
// pub downstream_transform: Option<DAffine2>,
// pub real_time: Option<f64>,
// pub animation_time: Option<f64>,
// pub index: Option<usize>,
// pub editor_var_args: Option<(Vec<String>, Vec<Arc<Box<[dyn std::any::Any + 'static + std::panic::UnwindSafe]>>>)>,
// TODO: Temporarily used to execute with RenderConfig as call argument, will be removed once these fields can be passed
// As a scope input to the reworked render node. This will allow the Editor Context to be used to evaluate any node
pub render_config: RenderConfig,
}
unsafe impl StaticType for EditorContext {
type Static = EditorContext;
}
// impl Default for EditorContext {
// fn default() -> Self {
// EditorContext {
// footprint: None,
// downstream_transform: None,
// real_time: None,
// animation_time: None,
// index: None,
// // editor_var_args: None,
// }
// }
// }
// impl EditorContext {
// pub fn to_context(&self) -> graphene_std::Context {
// let mut context = OwnedContextImpl::default();
// if let Some(footprint) = self.footprint {
// context.set_footprint(footprint);
// }
// if let Some(footprint) = self.footprint {
// context.set_footprint(footprint);
// }
// if let Some(downstream_transform) = self.downstream_transform {
// context.set_downstream_transform(downstream_transform);
// }
// if let Some(real_time) = self.real_time {
// context.set_real_time(real_time);
// }
// if let Some(animation_time) = self.animation_time {
// context.set_animation_time(animation_time);
// }
// if let Some(index) = self.index {
// context.set_index(index);
// }
// // if let Some(editor_var_args) = self.editor_var_args {
// // let (variable_names, values)
// // context.set_varargs((variable_names, values))
// // }
// context.into_context()
// }
// }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IntrospectError {
PathNotFound(Vec<NodeId>),
ProtoNodeNotFound(SNI),
ProtoNodeNotFound(CompiledProtonodeInput),
InputIndexOutOfBounds(CompiledProtonodeInput),
InvalidInputType(CompiledProtonodeInput),
NoData,
RuntimeNotReady,
IntrospectNotImplemented,
@ -236,14 +176,33 @@ 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(id) => write!(f, "ProtoNode not found: {:?}", id),
IntrospectError::ProtoNodeNotFound(input) => write!(f, "ProtoNode not found: {:?}", input),
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),
}
}
}
#[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
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 store of dynamically typed nodes and their associated source map.
///
/// [`BorrowTree`] maintains two main data structures:
@ -264,51 +223,54 @@ impl std::fmt::Display for IntrospectError {
/// A store of the dynamically typed nodes and also the source map.
#[derive(Default, Clone)]
pub struct BorrowTree {
// A hashmap of node IDs and dynamically typed nodes, as well as the number of inserted monitor nodes
nodes: HashMap<SNI, SharedNodeContainer>,
// A hashmap of node IDs to dynamically typed proto nodes, as well as the auto inserted MonitorCache nodes, and editor entry point
nodes: HashMap<SNI, InsertedProtonode>,
}
impl BorrowTree {
pub async fn new(proto_network: Vec<ProtoNode>, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
let mut nodes = BorrowTree::default();
for node in proto_network {
for node in proto_network.into_nodes() {
nodes.push_node(node, typing_context).await?
}
Ok(nodes)
}
/// Pushes new nodes into the tree and returns a vec of document nodes that had their types changed, and a vec of all nodes that were removed (including auto inserted value nodes)
pub async fn update(&mut self, proto_network: Vec<ProtoNode>, typing_context: &TypingContext) -> Result<(Vec<SNI>, HashSet<SNI>), GraphErrors> {
pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec<SNI>, HashSet<SNI>), GraphErrors> {
let mut old_nodes = self.nodes.keys().copied().into_iter().collect::<HashSet<_>>();
// List of all document node paths that need to be updated, which occurs if their path changes or type changes
let mut nodes_with_new_type = Vec::new();
for node in proto_network {
for node in proto_network.into_nodes() {
let sni = node.stable_node_id;
old_nodes.remove(&sni);
let sni = node.stable_node_id;
if !self.nodes.contains_key(&sni) {
if node.original_location.send_types_to_editor {
// Do not send types for auto inserted value nodes
if matches!(node.construction_args, ConstructionArgs::Nodes(_)) {
nodes_with_new_type.push(sni)
}
self.push_node(node, typing_context);
self.push_node(node, typing_context).await?;
}
}
Ok((nodes_with_new_type, old_nodes))
}
fn node_deps(&self, nodes: &[SNI]) -> Vec<SharedNodeContainer> {
nodes.iter().map(|node| self.nodes.get(node).unwrap().clone()).collect()
fn node_deps(&self, input_metadata: &Vec<Option<UpstreamInputMetadata>>) -> Vec<&InsertedProtonode> {
input_metadata
.iter()
.map(|input_metadata| self.nodes.get(&input_metadata.as_ref().expect("input should be mapped during SNI generation").input_sni).unwrap())
.collect()
}
/// Evaluate the output node of the [`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).cloned()?;
let output = node.eval(Box::new(input));
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.
@ -317,8 +279,8 @@ impl BorrowTree {
where
I: StaticType + 'static + Send + Sync,
{
let inserted_node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
let output = inserted_node.eval(Box::new(input));
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));
TaggedValue::try_from_any(output.await)
}
@ -377,12 +339,9 @@ 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, inputs: usize) {
self.nodes.remove(&id);
// Also remove all corresponding monitor nodes
for monitor_index in 1..=inputs {
self.nodes.remove(&NodeId(id.0 + monitor_index as u64));
}
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())
}
/// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`.
@ -400,40 +359,115 @@ impl BorrowTree {
/// - `Nodes`: Constructs a node using other nodes as dependencies.
/// - Uses the constructor function from the `typing_context` for `Nodes` construction arguments.
/// - Returns an error if no constructor is found for the given node ID.
/// 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) => {
ConstructionArgs::Value(value_args) => {
// The constructor for nodes with value construction args (value nodes) is not called.
// It is not necessary to clone the Arc for the wasm editor api, since the value node is deduplicated and only called once.
// It is cloned whenever it is evaluated
let upcasted = UpcastNode::new(value);
// let node = if let TaggedValue::ApplicationIo(api) = &*value {
// let editor_api = UpcastAsRefNode::new(api.clone());
// let node = Box::new(editor_api) as TypeErasedBox<'_>;
// NodeContainer::new(node)
// } else {
let upcasted = UpcastNode::new(value_args.value);
let node = Box::new(upcasted) as TypeErasedBox<'_>;
self.nodes.insert(sni, NodeContainer::new(node));
let value_node = 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(),
};
self.nodes.insert(sni, inserted_protonode);
}
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
ConstructionArgs::Nodes(ref node_construction_args) => {
ConstructionArgs::Nodes(node_construction_args) => {
let construction_nodes = self.node_deps(&node_construction_args.inputs);
let types = typing_context.type_of(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let monitor_nodes = construction_nodes
.into_iter()
.enumerate()
.map(|(input_index, construction_node)| {
let input_type = types.inputs.get(input_index).unwrap(); //.ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let monitor_constructor = typing_context.monitor_constructor(input_type).unwrap(); // .ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let monitor = monitor_constructor(construction_node);
let monitor_node_container = NodeContainer::new(monitor);
self.nodes.insert(NodeId(sni.0 + input_index as u64 + 1), monitor_node_container.clone());
monitor_node_container
})
.collect();
let 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 constructor = typing_context.constructor(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let node = constructor(monitor_nodes).await;
let node = NodeContainer::new(node);
self.nodes.insert(sni, node);
// Insert nullification if necessary
let protonode_inputs = construction_nodes
.iter()
.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 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);
let cache_node_container = NodeContainer::new(cache);
cache_node_container
} else {
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);
let inserted_protonode = InsertedProtonode {
is_value: false,
cached_protonode,
output_editor_entrypoint,
input_thumbnail_entrypoints,
input_introspection_entrypoints,
};
self.nodes.insert(sni, inserted_protonode);
}
};
Ok(())
@ -449,7 +483,7 @@ mod test {
#[test]
fn push_node_sync() {
let mut tree = BorrowTree::default();
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![], NodeId(0));
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), NodeId(0));
let context = TypingContext::default();
let future = tree.push_node(val_1_protonode, &context);
futures::executor::block_on(future).unwrap();

View file

@ -1,7 +1,7 @@
use dyn_any::StaticType;
use glam::{DVec2, IVec2, UVec2};
use graph_craft::document::value::RenderOutput;
use graph_craft::proto::{MonitorConstructor, NodeConstructor, TypeErasedBox};
use graph_craft::proto::{CacheConstructor, NodeConstructor, TypeErasedBox};
use graphene_core::raster::color::Color;
use graphene_core::raster::*;
use graphene_core::raster_types::{CPU, GPU, RasterDataTable};
@ -17,8 +17,8 @@ use graphene_std::any::DowncastBothNode;
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode};
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
#[cfg(feature = "gpu")]
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle};
use node_registry_macros::{async_node, convert_node, into_node, monitor_node};
use graphene_std::wasm_application_io::{WasmApplicationIoValue, WasmSurfaceHandle};
use node_registry_macros::{async_node, cache_node, convert_node, into_node};
use once_cell::sync::Lazy;
use std::collections::HashMap;
#[cfg(feature = "gpu")]
@ -119,22 +119,22 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<GPU>]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RasterDataTable<GPU>]),
#[cfg(feature = "gpu")]
into_node!(from: &WasmEditorApi, to: &WgpuExecutor),
// #[cfg(feature = "gpu")]
// into_node!(from: &WasmApplicationIoValue, to: &WgpuExecutor),
#[cfg(feature = "gpu")]
(
ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)),
|args| {
Box::pin(async move {
let editor_api: DowncastBothNode<Context, &WasmEditorApi> = DowncastBothNode::new(args[0].clone());
let editor_api: DowncastBothNode<Context, Arc<WasmApplicationIoValue>> = DowncastBothNode::new(args[0].clone());
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(editor_api);
let any: DynAnyNode<Context, _, _> = DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, dyn_any::DynFuture<'static, &WasmEditorApi>>::new());
let params = vec![fn_type_fut!(Context, &WasmEditorApi)];
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, dyn_any::DynFuture<'static, Arc<WasmApplicationIoValue>>>::new());
let params = vec![fn_type_fut!(Context, Arc<WasmApplicationIoValue>)];
let mut node_io = <wgpu_executor::CreateGpuSurfaceNode<_> as NodeIO<'_, Context>>::to_async_node_io(&node, params);
node_io.call_argument = concrete!(<Context as StaticType>::Static);
node_io
@ -192,51 +192,51 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
pub static NODE_REGISTRY: Lazy<HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>> = Lazy::new(|| node_registry());
fn monitor_nodes() -> HashMap<Type, MonitorConstructor> {
let nodes: Vec<(Type, MonitorConstructor)> = vec![
monitor_node!(ImageTexture),
monitor_node!(VectorDataTable),
monitor_node!(GraphicGroupTable),
monitor_node!(GraphicElement),
monitor_node!(Artboard),
monitor_node!(RasterDataTable<CPU>),
monitor_node!(RasterDataTable<GPU>),
monitor_node!(graphene_core::instances::Instances<Artboard>),
monitor_node!(String),
monitor_node!(IVec2),
monitor_node!(DVec2),
monitor_node!(bool),
monitor_node!(f64),
monitor_node!(u32),
monitor_node!(u64),
monitor_node!(()),
monitor_node!(Vec<f64>),
monitor_node!(BlendMode),
monitor_node!(graphene_std::transform::ReferencePoint),
monitor_node!(graphene_path_bool::BooleanOperation),
monitor_node!(Option<Color>),
monitor_node!(graphene_core::vector::style::Fill),
monitor_node!(graphene_core::vector::style::StrokeCap),
monitor_node!(graphene_core::vector::style::StrokeJoin),
monitor_node!(graphene_core::vector::style::PaintOrder),
monitor_node!(graphene_core::vector::style::StrokeAlign),
monitor_node!(graphene_core::vector::style::Stroke),
monitor_node!(graphene_core::vector::style::Gradient),
monitor_node!(graphene_core::vector::style::GradientStops),
monitor_node!(Vec<graphene_core::uuid::NodeId>),
monitor_node!(Color),
monitor_node!(Box<graphene_core::vector::VectorModification>),
monitor_node!(graphene_std::vector::misc::CentroidType),
monitor_node!(graphene_std::vector::misc::PointSpacingType),
fn cache_nodes() -> HashMap<Type, CacheConstructor> {
let nodes: Vec<(Type, CacheConstructor)> = vec![
cache_node!(ImageTexture),
cache_node!(VectorDataTable),
cache_node!(GraphicGroupTable),
cache_node!(GraphicElement),
cache_node!(Artboard),
cache_node!(RasterDataTable<CPU>),
cache_node!(RasterDataTable<GPU>),
cache_node!(graphene_core::instances::Instances<Artboard>),
cache_node!(String),
cache_node!(IVec2),
cache_node!(DVec2),
cache_node!(bool),
cache_node!(f64),
cache_node!(u32),
cache_node!(u64),
cache_node!(()),
cache_node!(Vec<f64>),
cache_node!(BlendMode),
cache_node!(graphene_std::transform::ReferencePoint),
cache_node!(graphene_path_bool::BooleanOperation),
cache_node!(Option<Color>),
cache_node!(graphene_core::vector::style::Fill),
cache_node!(graphene_core::vector::style::StrokeCap),
cache_node!(graphene_core::vector::style::StrokeJoin),
cache_node!(graphene_core::vector::style::PaintOrder),
cache_node!(graphene_core::vector::style::StrokeAlign),
cache_node!(graphene_core::vector::style::Stroke),
cache_node!(graphene_core::vector::style::Gradient),
cache_node!(graphene_core::vector::style::GradientStops),
cache_node!(Vec<graphene_core::uuid::NodeId>),
cache_node!(Color),
cache_node!(Box<graphene_core::vector::VectorModification>),
cache_node!(graphene_std::vector::misc::CentroidType),
cache_node!(graphene_std::vector::misc::PointSpacingType),
];
let mut monitor_nodes = HashMap::new();
for (monitor_type, constructor) in nodes {
monitor_nodes.insert(monitor_type, constructor);
let mut cache_nodes = HashMap::new();
for (cache_type, constructor) in nodes {
cache_nodes.insert(cache_type, constructor);
}
monitor_nodes
cache_nodes
}
pub static MONITOR_NODES: Lazy<HashMap<Type, MonitorConstructor>> = Lazy::new(|| monitor_nodes());
pub static CACHE_NODES: Lazy<HashMap<Type, CacheConstructor>> = Lazy::new(|| cache_nodes());
mod node_registry_macros {
macro_rules! async_node {
@ -331,10 +331,10 @@ mod node_registry_macros {
};
}
macro_rules! monitor_node {
macro_rules! cache_node {
($type:ty) => {
(concrete!($type), |arg| {
let node = <graphene_core::memo::MonitorNode<graphene_std::Context, _, _>>::new(graphene_std::registry::downcast_node::<graphene_std::Context, $type>(arg));
let node = <graphene_core::memo::MonitorMemoNode<_, _>>::new(graphene_std::registry::downcast_node::<graphene_std::Context, $type>(arg));
let any: DynAnyNode<_, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
@ -342,7 +342,7 @@ mod node_registry_macros {
}
pub(crate) use async_node;
pub(crate) use cache_node;
pub(crate) use convert_node;
pub(crate) use into_node;
pub(crate) use monitor_node;
}

View file

@ -1,14 +1,18 @@
use graph_craft::ProtoNodeIdentifier;
use graph_craft::concrete;
use graph_craft::document::value::EditorMetadata;
use graph_craft::document::value::RenderOutput;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
use graph_craft::generic;
use graph_craft::wasm_application_io::WasmEditorApi;
use graph_craft::wasm_application_io::WasmApplicationIo;
use graphene_std::Context;
use graphene_std::application_io::ApplicationIoValue;
use graphene_std::text::FontCache;
use graphene_std::uuid::NodeId;
use std::sync::Arc;
pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEditorApi>) -> NodeNetwork {
pub fn wrap_network_in_scope(network: NodeNetwork, font_cache: Arc<FontCache>, editor_metadata: EditorMetadata, application_io: Arc<WasmApplicationIo>) -> NodeNetwork {
let inner_network = DocumentNode {
implementation: DocumentNodeImplementation::Network(network),
inputs: vec![],
@ -16,12 +20,12 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
};
let render_node = DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)],
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
inputs: vec![NodeInput::scope("application-io")],
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
skip_deduplication: true,
@ -35,9 +39,10 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
},
// TODO: Add conversion step
DocumentNode {
manual_composition: Some(concrete!(graphene_std::application_io::RenderConfig)),
manual_composition: Some(concrete!(Context)),
inputs: vec![
NodeInput::scope("editor-api"),
NodeInput::scope("editor-metadata"),
NodeInput::scope("application-io"),
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(generic!(T))), 0),
NodeInput::node(NodeId(1), 0),
],
@ -58,9 +63,16 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
let nodes = vec![inner_network, render_node];
NodeNetwork {
// exports: vec![NodeInput::value(TaggedValue::RenderOutput(RenderOutput::default()), true)],
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: nodes.into_iter().enumerate().map(|(id, node)| (NodeId(id as u64), node)).collect(),
scope_injections: [("editor-api".to_string(), TaggedValue::EditorApi(editor_api))].into_iter().collect(),
scope_injections: [
("font-cache".to_string(), TaggedValue::FontCache(font_cache)),
("editor-metadata".to_string(), TaggedValue::EditorMetadata(editor_metadata)),
("application-io".to_string(), TaggedValue::ApplicationIo(Arc::new(ApplicationIoValue(Some(application_io))))),
]
.into_iter()
.collect(),
// TODO(TrueDoctor): check if it makes sense to set `generated` to `true`
generated: false,
}

View file

@ -7,7 +7,7 @@ use std::sync::atomic::AtomicU64;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{Error, Ident, PatIdent, Token, WhereClause, WherePredicate, parse_quote};
use syn::{Error, Ident, PatIdent, Token, TypeParamBound, WhereClause, WherePredicate, parse_quote};
static NODE_ID: AtomicU64 = AtomicU64::new(0);
pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStream2> {
@ -346,6 +346,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
let properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));
let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, &graphene_core, &identifier);
let context_dependencies = input.context_dependency.clone();
Ok(quote! {
/// Underlying implementation for [#struct_name]
#[inline]
@ -373,10 +375,10 @@ 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};
use gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO, ContextDependency};
use gcore::value::ClonedNode;
use gcore::ops::TypeNode;
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, NODE_CONTEXT_DEPENDENCY, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
use gcore::ctor::ctor;
// Use the types specified in the implementation
@ -429,6 +431,17 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
};
NODE_METADATA.lock().unwrap().insert(#identifier(), metadata);
}
#[cfg_attr(not(target_arch = "wasm32"), ctor)]
fn register_context_dependency() {
let mut context_dependency = NODE_CONTEXT_DEPENDENCY.lock().unwrap();
context_dependency.insert(
#identifier,
vec![
#(ContextDependency::#context_dependencies,)*
]
);
}
}
})
}
@ -602,7 +615,6 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
));
}
let registry_name = format_ident!("__node_registry_{}_{}", NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), struct_name);
Ok(quote! {
#[cfg_attr(not(target_arch = "wasm32"), ctor)]
@ -615,11 +627,13 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
]
);
}
#[cfg(target_arch = "wasm32")]
#[unsafe(no_mangle)]
extern "C" fn #registry_name() {
register_node();
register_metadata();
register_context_dependency();
}
})
}

View file

@ -135,6 +135,7 @@ pub(crate) struct Input {
pub(crate) pat_ident: PatIdent,
pub(crate) ty: Type,
pub(crate) implementations: Punctuated<Type, Comma>,
pub(crate) context_dependency: Vec<proc_macro2::TokenStream>,
}
impl Parse for Implementation {
@ -350,6 +351,7 @@ fn parse_inputs(inputs: &Punctuated<FnArg, Comma>) -> syn::Result<(Input, Vec<Pa
pat_ident,
ty: (**ty).clone(),
implementations,
context_dependency: Vec::new(),
});
} else if let Pat::Ident(pat_ident) = &**pat {
let field = parse_field(pat_ident.clone(), (**ty).clone(), attrs).map_err(|e| Error::new_spanned(pat_ident, format!("Failed to parse argument '{}': {}", pat_ident.ident, e)))?;
@ -630,6 +632,24 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
impl ParsedNodeFn {
fn replace_impl_trait_in_input(&mut self) {
if let Type::ImplTrait(impl_trait) = self.input.ty.clone() {
let mut dependency_tokens = Vec::new();
for bound in &impl_trait.bounds {
if let syn::TypeParamBound::Trait(trait_bound) = bound {
if let Some(ident) = trait_bound.path.get_ident() {
match ident.to_string().as_str() {
"ExtractFootprint" => dependency_tokens.push(quote::quote! {ExtractFootprint}),
"ExtractDownstreamTransform" => dependency_tokens.push(quote::quote! {ExtractDownstreamTransform}),
"ExtractRealTime" => dependency_tokens.push(quote::quote! {ExtractRealTime}),
"ExtractAnimationTime" => dependency_tokens.push(quote::quote! {ExtractAnimationTime}),
"ExtractIndex" => dependency_tokens.push(quote::quote! {ExtractIndex}),
"ExtractVarArgs" => dependency_tokens.push(quote::quote! {ExtractVarArgs}),
_ => {}
}
}
}
}
self.input.context_dependency = dependency_tokens;
let ident = Ident::new("_Input", impl_trait.span());
let mut bounds = impl_trait.bounds;
bounds.push(parse_quote!('n));
@ -768,6 +788,7 @@ mod tests {
pat_ident: pat_ident("a"),
ty: parse_quote!(f64),
implementations: Punctuated::new(),
context_dependency: Vec::new(),
},
output_type: parse_quote!(f64),
is_async: false,
@ -829,6 +850,7 @@ mod tests {
pat_ident: pat_ident("footprint"),
ty: parse_quote!(Footprint),
implementations: Punctuated::new(),
context_dependency: Vec::new(),
},
output_type: parse_quote!(T),
is_async: false,
@ -901,6 +923,7 @@ mod tests {
pat_ident: pat_ident("_"),
ty: parse_quote!(impl Ctx),
implementations: Punctuated::new(),
context_dependency: Vec::new(),
},
output_type: parse_quote!(VectorData),
is_async: false,
@ -958,6 +981,7 @@ mod tests {
pat_ident: pat_ident("image"),
ty: parse_quote!(RasterDataTable<P>),
implementations: Punctuated::new(),
context_dependency: Vec::new(),
},
output_type: parse_quote!(RasterDataTable<P>),
is_async: false,
@ -1027,6 +1051,7 @@ mod tests {
pat_ident: pat_ident("a"),
ty: parse_quote!(f64),
implementations: Punctuated::new(),
context_dependency: Vec::new(),
},
output_type: parse_quote!(f64),
is_async: false,
@ -1084,6 +1109,7 @@ mod tests {
pat_ident: pat_ident("api"),
ty: parse_quote!(&WasmEditorApi),
implementations: Punctuated::new(),
context_dependency: Vec::new(),
},
output_type: parse_quote!(RasterDataTable<CPU>),
is_async: true,
@ -1141,6 +1167,7 @@ mod tests {
pat_ident: pat_ident("input"),
ty: parse_quote!(i32),
implementations: Punctuated::new(),
context_dependency: Vec::new(),
},
output_type: parse_quote!(i32),
is_async: false,

View file

@ -4,7 +4,7 @@ use anyhow::Result;
pub use context::Context;
use dyn_any::StaticType;
use glam::UVec2;
use graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle};
use graphene_application_io::{ApplicationIo, ApplicationIoValue, SurfaceHandle};
use graphene_core::{Color, Ctx};
pub use graphene_svg_renderer::RenderContext;
use std::sync::Arc;
@ -23,9 +23,9 @@ impl std::fmt::Debug for WgpuExecutor {
}
}
impl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<&'a EditorApi<T>> for &'a WgpuExecutor {
fn from(editor_api: &'a EditorApi<T>) -> Self {
editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap()
impl<'a, Io: ApplicationIo<Executor = WgpuExecutor>> From<&'a Arc<ApplicationIoValue<Io>>> for &'a WgpuExecutor {
fn from(application_io: &'a Arc<ApplicationIoValue<Io>>) -> Self {
application_io.0.as_ref().unwrap().gpu_executor().unwrap()
}
}
@ -153,8 +153,8 @@ impl WgpuExecutor {
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
#[node_macro::node(skip_impl)]
fn create_gpu_surface<'a: 'n, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + 'a + Send + Sync>(_: impl Ctx + 'a, editor_api: &'a EditorApi<Io>) -> Option<WgpuSurface> {
let canvas = editor_api.application_io.as_ref()?.window()?;
let executor = editor_api.application_io.as_ref()?.gpu_executor()?;
fn create_gpu_surface<'a: 'n, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + 'a + Send + Sync>(_: impl Ctx + 'a, application_io: Arc<ApplicationIoValue<Io>>) -> Option<WgpuSurface> {
let canvas = application_io.0.as_ref()?.window()?;
let executor = application_io.0.as_ref()?.gpu_executor()?;
Some(Arc::new(executor.create_surface(canvas).ok()?))
}