Merge branch 'master' into operation-tool

This commit is contained in:
0SlowPoke0
2025-08-26 02:40:37 +05:30
committed by GitHub
84 changed files with 642 additions and 1109 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+45 -6
View File
@@ -2,7 +2,7 @@ use crate::CustomEvent;
use crate::cef::WindowSize; use crate::cef::WindowSize;
use crate::consts::{APP_NAME, CEF_MESSAGE_LOOP_MAX_ITERATIONS}; use crate::consts::{APP_NAME, CEF_MESSAGE_LOOP_MAX_ITERATIONS};
use crate::render::GraphicsState; use crate::render::GraphicsState;
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage}; use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform};
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages}; use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
use rfd::AsyncFileDialog; use rfd::AsyncFileDialog;
@@ -35,6 +35,8 @@ pub(crate) struct WinitApp {
last_ui_update: Instant, last_ui_update: Instant,
avg_frame_time: f32, avg_frame_time: f32,
start_render_sender: SyncSender<()>, start_render_sender: SyncSender<()>,
web_communication_initialized: bool,
web_communication_startup_buffer: Vec<Vec<u8>>,
} }
impl WinitApp { impl WinitApp {
@@ -61,6 +63,8 @@ impl WinitApp {
last_ui_update: Instant::now(), last_ui_update: Instant::now(),
avg_frame_time: 0., avg_frame_time: 0.,
start_render_sender, start_render_sender,
web_communication_initialized: false,
web_communication_startup_buffer: Vec::new(),
} }
} }
@@ -71,7 +75,7 @@ impl WinitApp {
tracing::error!("Failed to serialize frontend messages"); tracing::error!("Failed to serialize frontend messages");
return; return;
}; };
self.cef_context.send_web_message(bytes); self.send_or_queue_web_message(bytes);
} }
DesktopFrontendMessage::OpenFileDialog { title, filters, context } => { DesktopFrontendMessage::OpenFileDialog { title, filters, context } => {
let event_loop_proxy = self.event_loop_proxy.clone(); let event_loop_proxy = self.event_loop_proxy.clone();
@@ -148,6 +152,15 @@ impl WinitApp {
graphics_state.set_overlays_scene(scene); graphics_state.set_overlays_scene(scene);
} }
} }
DesktopFrontendMessage::UpdateWindowState { maximized, minimized } => {
if let Some(window) = &self.window {
window.set_maximized(maximized);
window.set_minimized(minimized);
}
}
DesktopFrontendMessage::CloseWindow => {
let _ = self.event_loop_proxy.send_event(CustomEvent::CloseWindow);
}
} }
} }
@@ -161,6 +174,14 @@ impl WinitApp {
let responses = self.desktop_wrapper.dispatch(message); let responses = self.desktop_wrapper.dispatch(message);
self.handle_desktop_frontend_messages(responses); self.handle_desktop_frontend_messages(responses);
} }
fn send_or_queue_web_message(&mut self, message: Vec<u8>) {
if self.web_communication_initialized {
self.cef_context.send_web_message(message);
} else {
self.web_communication_startup_buffer.push(message);
}
}
} }
impl ApplicationHandler<CustomEvent> for WinitApp { impl ApplicationHandler<CustomEvent> for WinitApp {
@@ -211,10 +232,24 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
tracing::info!("Winit window created and ready"); tracing::info!("Winit window created and ready");
self.desktop_wrapper.init(self.wgpu_context.clone()); self.desktop_wrapper.init(self.wgpu_context.clone());
#[cfg(target_os = "windows")]
let platform = Platform::Windows;
#[cfg(target_os = "macos")]
let platform = Platform::Mac;
#[cfg(target_os = "linux")]
let platform = Platform::Linux;
self.dispatch_desktop_wrapper_message(DesktopWrapperMessage::UpdatePlatform(platform));
} }
fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) { fn user_event(&mut self, event_loop: &ActiveEventLoop, event: CustomEvent) {
match event { match event {
CustomEvent::WebCommunicationInitialized => {
self.web_communication_initialized = true;
for message in self.web_communication_startup_buffer.drain(..) {
self.cef_context.send_web_message(message);
}
}
CustomEvent::DesktopWrapperMessage(message) => self.dispatch_desktop_wrapper_message(message), CustomEvent::DesktopWrapperMessage(message) => self.dispatch_desktop_wrapper_message(message),
CustomEvent::NodeGraphExecutionResult(result) => match result { CustomEvent::NodeGraphExecutionResult(result) => match result {
NodeGraphExecutionResult::HasRun(texture) => { NodeGraphExecutionResult::HasRun(texture) => {
@@ -250,6 +285,12 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
self.cef_schedule = Some(instant); self.cef_schedule = Some(instant);
} }
} }
CustomEvent::CloseWindow => {
// TODO: Implement graceful shutdown
tracing::info!("Exiting main event loop");
event_loop.exit();
}
} }
} }
@@ -258,14 +299,12 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
tracing::info!("The close button was pressed; stopping"); let _ = self.event_loop_proxy.send_event(CustomEvent::CloseWindow);
event_loop.exit();
} }
WindowEvent::Resized(PhysicalSize { width, height }) => { WindowEvent::Resized(PhysicalSize { width, height }) => {
let _ = self.window_size_sender.send(WindowSize::new(width as usize, height as usize)); let _ = self.window_size_sender.send(WindowSize::new(width as usize, height as usize));
self.cef_context.notify_of_resize(); self.cef_context.notify_of_resize();
} }
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
let Some(ref mut graphics_state) = self.graphics_state else { return }; let Some(ref mut graphics_state) = self.graphics_state else { return };
// Only rerender once we have a new ui texture to display // Only rerender once we have a new ui texture to display
+5
View File
@@ -46,6 +46,7 @@ pub(crate) trait CefEventHandler: Clone {
/// Scheudule the main event loop to run the cef event loop after the timeout /// Scheudule the main event loop to run the cef event loop after the timeout
/// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation. /// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant); fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
fn initialized_web_communication(&self);
fn receive_web_message(&self, message: &[u8]); fn receive_web_message(&self, message: &[u8]);
} }
@@ -145,6 +146,10 @@ impl CefEventHandler for CefHandler {
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time));
} }
fn initialized_web_communication(&self) {
let _ = self.event_loop_proxy.send_event(CustomEvent::WebCommunicationInitialized);
}
fn receive_web_message(&self, message: &[u8]) { fn receive_web_message(&self, message: &[u8]) {
let Some(desktop_wrapper_message) = deserialize_editor_message(message) else { let Some(desktop_wrapper_message) = deserialize_editor_message(message) else {
tracing::error!("Failed to deserialize web message"); tracing::error!("Failed to deserialize web message");
@@ -32,6 +32,10 @@ impl<H: CefEventHandler> ImplClient for BrowserProcessClientImpl<H> {
) -> ::std::os::raw::c_int { ) -> ::std::os::raw::c_int {
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) }; let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
match unpacked_message { match unpacked_message {
Some(UnpackedMessage {
message_type: MessageType::Initialized,
data: _,
}) => self.event_handler.initialized_web_communication(),
Some(UnpackedMessage { Some(UnpackedMessage {
message_type: MessageType::SendToNative, message_type: MessageType::SendToNative,
data, data,
@@ -76,24 +76,30 @@ impl ImplRenderProcessHandler for RenderProcessHandlerImpl {
} }
fn on_context_created(&self, _browser: Option<&mut cef::Browser>, _frame: Option<&mut cef::Frame>, context: Option<&mut cef::V8Context>) { fn on_context_created(&self, _browser: Option<&mut cef::Browser>, _frame: Option<&mut cef::Frame>, context: Option<&mut cef::V8Context>) {
let function_name = "sendNativeMessage"; let register_js_function = |context: &mut cef::V8Context, name: &'static str| {
let mut v8_handler = V8Handler::new(BrowserProcessV8HandlerImpl::new());
let Some(mut function) = v8_value_create_function(Some(&CefString::from(name)), Some(&mut v8_handler)) else {
tracing::error!("Failed to create V8 function {name}");
return;
};
let Some(global) = context.global() else {
tracing::error!("Global object is not available in V8 context");
return;
};
global.set_value_bykey(Some(&CefString::from(name)), Some(&mut function), V8Propertyattribute::default());
};
let Some(context) = context else { let Some(context) = context else {
tracing::error!("V8 context is not available"); tracing::error!("V8 context is not available");
return; return;
}; };
let mut v8_handler = V8Handler::new(BrowserProcessV8HandlerImpl::new()); let initialized_function_name = "initializeNativeCommunication";
let Some(mut function) = v8_value_create_function(Some(&CefString::from(function_name)), Some(&mut v8_handler)) else { let send_function_name = "sendNativeMessage";
tracing::error!("Failed to create V8 function {function_name}");
return;
};
let Some(global) = context.global() else { register_js_function(context, initialized_function_name);
tracing::error!("Global object is not available in V8 context"); register_js_function(context, send_function_name);
return;
};
global.set_value_bykey(Some(&CefString::from(function_name)), Some(&mut function), V8Propertyattribute::default());
} }
fn get_raw(&self) -> *mut _cef_render_process_handler_t { fn get_raw(&self) -> *mut _cef_render_process_handler_t {
@@ -21,8 +21,11 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl {
_retval: Option<&mut Option<V8Value>>, _retval: Option<&mut Option<V8Value>>,
_exception: Option<&mut cef::CefString>, _exception: Option<&mut cef::CefString>,
) -> ::std::os::raw::c_int { ) -> ::std::os::raw::c_int {
if let Some(name) = name { match name.map(|s| s.to_string()).unwrap_or_default().as_str() {
if name.to_string() == "sendNativeMessage" { "initializeNativeCommunication" => {
v8_context_get_current_context().send_message(MessageType::Initialized, vec![0u8].as_slice());
}
"sendNativeMessage" => {
let Some(args) = arguments else { let Some(args) = arguments else {
tracing::error!("No arguments provided to sendNativeMessage"); tracing::error!("No arguments provided to sendNativeMessage");
return 0; return 0;
@@ -48,6 +51,9 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl {
return 1; return 1;
} }
name => {
tracing::error!("Unknown V8 function called: {}", name);
}
} }
1 1
} }
+6
View File
@@ -1,12 +1,17 @@
use cef::{CefString, Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t}; use cef::{CefString, Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};
pub(crate) enum MessageType { pub(crate) enum MessageType {
Initialized,
SendToJS, SendToJS,
SendToNative, SendToNative,
} }
impl From<MessageType> for MessageInfo { impl From<MessageType> for MessageInfo {
fn from(val: MessageType) -> Self { fn from(val: MessageType) -> Self {
match val { match val {
MessageType::Initialized => MessageInfo {
name: "initialized".to_string(),
target: cef_process_id_t::PID_BROWSER.into(),
},
MessageType::SendToJS => MessageInfo { MessageType::SendToJS => MessageInfo {
name: "send_to_js".to_string(), name: "send_to_js".to_string(),
target: cef_process_id_t::PID_RENDERER.into(), target: cef_process_id_t::PID_RENDERER.into(),
@@ -22,6 +27,7 @@ impl TryFrom<String> for MessageType {
type Error = (); type Error = ();
fn try_from(value: String) -> Result<Self, Self::Error> { fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() { match value.as_str() {
"initialized" => Ok(MessageType::Initialized),
"send_to_js" => Ok(MessageType::SendToJS), "send_to_js" => Ok(MessageType::SendToJS),
"send_to_native" => Ok(MessageType::SendToNative), "send_to_native" => Ok(MessageType::SendToNative),
_ => Err(()), _ => Err(()),
+1
View File
@@ -87,6 +87,7 @@ impl<'a> GraphiteFrontendResourceHandler<'a> {
match ext { match ext {
"html" => Some("text/html".to_string()), "html" => Some("text/html".to_string()),
"css" => Some("text/css".to_string()), "css" => Some("text/css".to_string()),
"txt" => Some("text/plain".to_string()),
"wasm" => Some("application/wasm".to_string()), "wasm" => Some("application/wasm".to_string()),
"js" => Some("application/javascript".to_string()), "js" => Some("application/javascript".to_string()),
"png" => Some("image/png".to_string()), "png" => Some("image/png".to_string()),
+2
View File
@@ -21,8 +21,10 @@ use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext};
pub(crate) enum CustomEvent { pub(crate) enum CustomEvent {
UiUpdate(wgpu::Texture), UiUpdate(wgpu::Texture),
ScheduleBrowserWork(Instant), ScheduleBrowserWork(Instant),
WebCommunicationInitialized,
DesktopWrapperMessage(DesktopWrapperMessage), DesktopWrapperMessage(DesktopWrapperMessage),
NodeGraphExecutionResult(NodeGraphExecutionResult), NodeGraphExecutionResult(NodeGraphExecutionResult),
CloseWindow,
} }
fn main() { fn main() {
@@ -1,6 +1,9 @@
use graphene_std::Color; use graphene_std::Color;
use graphene_std::raster::Image; use graphene_std::raster::Image;
use graphite_editor::messages::prelude::{DocumentMessage, PortfolioMessage}; use graphite_editor::messages::app_window::app_window_message_handler::AppWindowPlatform;
use graphite_editor::messages::prelude::{AppWindowMessage, DocumentMessage, PortfolioMessage};
use crate::messages::Platform;
use super::DesktopWrapperMessageDispatcher; use super::DesktopWrapperMessageDispatcher;
use super::messages::{DesktopFrontendMessage, DesktopWrapperMessage, EditorMessage, OpenFileDialogContext, SaveFileDialogContext}; use super::messages::{DesktopFrontendMessage, DesktopWrapperMessage, EditorMessage, OpenFileDialogContext, SaveFileDialogContext};
@@ -106,5 +109,14 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
dispatcher.queue_editor_message(message.into()); dispatcher.queue_editor_message(message.into());
} }
DesktopWrapperMessage::PollNodeGraphEvaluation => dispatcher.poll_node_graph_evaluation(), DesktopWrapperMessage::PollNodeGraphEvaluation => dispatcher.poll_node_graph_evaluation(),
DesktopWrapperMessage::UpdatePlatform(platform) => {
let platform = match platform {
Platform::Windows => AppWindowPlatform::Windows,
Platform::Mac => AppWindowPlatform::Mac,
Platform::Linux => AppWindowPlatform::Linux,
};
let message = AppWindowMessage::AppWindowUpdatePlatform { platform };
dispatcher.queue_editor_message(message.into());
}
} }
} }
@@ -64,6 +64,15 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
FrontendMessage::TriggerVisitLink { url } => { FrontendMessage::TriggerVisitLink { url } => {
dispatcher.respond(DesktopFrontendMessage::OpenUrl(url)); dispatcher.respond(DesktopFrontendMessage::OpenUrl(url));
} }
FrontendMessage::UpdateWindowState { maximized, minimized } => {
dispatcher.respond(DesktopFrontendMessage::UpdateWindowState { maximized, minimized });
// Forward this to update the ui
return Some(message);
}
FrontendMessage::CloseWindow => {
dispatcher.respond(DesktopFrontendMessage::CloseWindow);
}
m => return Some(m), m => return Some(m),
} }
None None
+12
View File
@@ -30,6 +30,11 @@ pub enum DesktopFrontendMessage {
height: f32, height: f32,
}, },
UpdateOverlays(vello::Scene), UpdateOverlays(vello::Scene),
UpdateWindowState {
maximized: bool,
minimized: bool,
},
CloseWindow,
} }
pub struct FileFilter { pub struct FileFilter {
@@ -47,6 +52,7 @@ pub enum DesktopWrapperMessage {
ImportSvg { path: PathBuf, content: Vec<u8> }, ImportSvg { path: PathBuf, content: Vec<u8> },
ImportImage { path: PathBuf, content: Vec<u8> }, ImportImage { path: PathBuf, content: Vec<u8> },
PollNodeGraphEvaluation, PollNodeGraphEvaluation,
UpdatePlatform(Platform),
} }
pub enum OpenFileDialogContext { pub enum OpenFileDialogContext {
@@ -58,3 +64,9 @@ pub enum SaveFileDialogContext {
Document { document_id: DocumentId, content: Vec<u8> }, Document { document_id: DocumentId, content: Vec<u8> },
File { content: Vec<u8> }, File { content: Vec<u8> },
} }
pub enum Platform {
Windows,
Mac,
Linux,
}
+1 -1
View File
@@ -442,7 +442,7 @@ mod test {
assert_eq!(layers_before_copy.len(), 3); assert_eq!(layers_before_copy.len(), 3);
assert_eq!(layers_after_copy.len(), 6); assert_eq!(layers_after_copy.len(), 6);
println!("{:?} {:?}", layers_after_copy, layers_before_copy); println!("{layers_after_copy:?} {layers_before_copy:?}");
assert_eq!(layers_after_copy[5], shape_id); assert_eq!(layers_after_copy[5], shape_id);
} }
@@ -1,9 +1,12 @@
use crate::messages::prelude::*; use crate::messages::prelude::*;
use super::app_window_message_handler::AppWindowPlatform;
#[impl_message(Message, AppWindow)] #[impl_message(Message, AppWindow)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum AppWindowMessage { pub enum AppWindowMessage {
AppWindowMinimize, AppWindowMinimize,
AppWindowMaximize, AppWindowMaximize,
AppWindowUpdatePlatform { platform: AppWindowPlatform },
AppWindowClose, AppWindowClose,
} }
@@ -6,34 +6,34 @@ use graphite_proc_macros::{ExtractField, message_handler_data};
pub struct AppWindowMessageHandler { pub struct AppWindowMessageHandler {
platform: AppWindowPlatform, platform: AppWindowPlatform,
maximized: bool, maximized: bool,
viewport_hole_punch_active: bool, minimized: bool,
} }
#[message_handler_data] #[message_handler_data]
impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler { impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) { fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
match message { match message {
AppWindowMessage::AppWindowMinimize => {
self.platform = if self.platform == AppWindowPlatform::Mac {
AppWindowPlatform::Windows
} else {
AppWindowPlatform::Mac
};
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
}
AppWindowMessage::AppWindowMaximize => { AppWindowMessage::AppWindowMaximize => {
self.maximized = !self.maximized; self.maximized = !self.maximized;
responses.add(FrontendMessage::UpdateMaximized { maximized: self.maximized }); responses.add(FrontendMessage::UpdateWindowState {
maximized: self.maximized,
self.viewport_hole_punch_active = !self.viewport_hole_punch_active; minimized: self.minimized,
responses.add(FrontendMessage::UpdateViewportHolePunch {
active: self.viewport_hole_punch_active,
}); });
} }
AppWindowMessage::AppWindowClose => { AppWindowMessage::AppWindowMinimize => {
self.platform = AppWindowPlatform::Web; self.minimized = !self.minimized;
responses.add(FrontendMessage::UpdateWindowState {
maximized: self.maximized,
minimized: self.minimized,
});
}
AppWindowMessage::AppWindowUpdatePlatform { platform } => {
self.platform = platform;
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform }); responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
} }
AppWindowMessage::AppWindowClose => {
responses.add(FrontendMessage::CloseWindow);
}
} }
} }
@@ -329,9 +329,11 @@ pub enum FrontendMessage {
UpdatePlatform { UpdatePlatform {
platform: AppWindowPlatform, platform: AppWindowPlatform,
}, },
UpdateMaximized { UpdateWindowState {
maximized: bool, maximized: bool,
minimized: bool,
}, },
CloseWindow,
UpdateViewportHolePunch { UpdateViewportHolePunch {
active: bool, active: bool,
}, },
@@ -479,7 +479,7 @@ impl<const LENGTH: usize> Iterator for BitVectorIter<'_, LENGTH> {
impl<const LENGTH: usize> Display for BitVector<LENGTH> { impl<const LENGTH: usize> Display for BitVector<LENGTH> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for storage in self.0.iter().rev() { for storage in self.0.iter().rev() {
write!(f, "{:0width$b}", storage, width = STORAGE_SIZE_BITS)?; write!(f, "{storage:0STORAGE_SIZE_BITS$b}")?;
} }
Ok(()) Ok(())
+7 -9
View File
@@ -70,13 +70,11 @@ mod test {
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) { fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
// Print the current node // Print the current node
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() { let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() {
("├── ", format!("{}│ ", prefix)) ("├── ", format!("{prefix}│ "))
} else if is_last {
("└── ", format!("{prefix} "))
} else { } else {
if is_last { ("├── ", format!("{prefix}│ "))
("└── ", format!("{} ", prefix))
} else {
("├── ", format!("{}│ ", prefix))
}
}; };
if tree.path().is_empty() { if tree.path().is_empty() {
@@ -101,7 +99,7 @@ mod test {
let is_last_field = i == len - 1; let is_last_field = i == len - 1;
let branch = if is_last_field { "└── " } else { "├── " }; let branch = if is_last_field { "└── " } else { "├── " };
file.write_all(format!("{}{}{}\n", child_prefix, branch, field).as_bytes()).unwrap(); file.write_all(format!("{child_prefix}{branch}{field}\n").as_bytes()).unwrap();
} }
} }
@@ -109,9 +107,9 @@ mod test {
if let Some(data) = tree.message_handler_fields() { if let Some(data) = tree.message_handler_fields() {
let len = data.fields().len(); let len = data.fields().len();
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() { let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() {
("├── ", format!("{}│ ", prefix)) ("├── ", format!("{prefix}│ "))
} else { } else {
("└── ", format!("{} ", prefix)) ("└── ", format!("{prefix} "))
}; };
const FRONTEND_MESSAGE_STR: &str = "FrontendMessage"; const FRONTEND_MESSAGE_STR: &str = "FrontendMessage";
@@ -1782,7 +1782,8 @@ impl DocumentMessageHandler {
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> { pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content) let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
.or_else(|_| { .or_else(|e| {
log::warn!("failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler");
// TODO: Eventually remove this document upgrade code // TODO: Eventually remove this document upgrade code
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct OldDocumentMessageHandler { pub struct OldDocumentMessageHandler {
@@ -91,6 +91,32 @@ pub fn get_current_normalized_pivot(inputs: &[NodeInput]) -> DVec2 {
if let Some(&TaggedValue::DVec2(pivot)) = inputs[5].as_value() { pivot } else { DVec2::splat(0.5) } if let Some(&TaggedValue::DVec2(pivot)) = inputs[5].as_value() { pivot } else { DVec2::splat(0.5) }
} }
/// Expand a bounds to avoid div zero errors
fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
let bounds_size = bounds_max - bounds_min;
if bounds_size.x < 1e-10 {
bounds_max.x = bounds_min.x + 1.;
}
if bounds_size.y < 1e-10 {
bounds_max.y = bounds_min.y + 1.;
}
[bounds_min, bounds_max]
}
/// Returns corners of all subpaths
fn subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
subpaths
.iter()
.filter_map(|subpath| subpath.bounding_box())
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
.unwrap_or_default()
}
/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
clamp_bounds(bounds_min, bounds_max)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -138,29 +164,3 @@ mod tests {
} }
} }
} }
/// Expand a bounds to avoid div zero errors
fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
let bounds_size = bounds_max - bounds_min;
if bounds_size.x < 1e-10 {
bounds_max.x = bounds_min.x + 1.;
}
if bounds_size.y < 1e-10 {
bounds_max.y = bounds_min.y + 1.;
}
[bounds_min, bounds_max]
}
/// Returns corners of all subpaths
fn subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
subpaths
.iter()
.filter_map(|subpath| subpath.bounding_box())
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
.unwrap_or_default()
}
/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
clamp_bounds(bounds_min, bounds_max)
}
@@ -46,7 +46,7 @@ impl NodePropertiesContext<'_> {
return None; return None;
}; };
widget_override_lambda(*node_id, index, self) widget_override_lambda(*node_id, index, self)
.map_err(|error| log::error!("Error in widget override lambda: {}", error)) .map_err(|error| log::error!("Error in widget override lambda: {error}"))
.ok() .ok()
} else { } else {
None None
@@ -111,7 +111,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::None, true)], inputs: vec![NodeInput::value(TaggedValue::None, true)],
manual_composition: Some(generic!(T)), call_argument: generic!(T),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
@@ -151,19 +151,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)], inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
] ]
@@ -233,33 +233,33 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)], inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
..Default::default() ..Default::default()
}, },
// Secondary (left) input type coercion // Secondary (left) input type coercion
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 1)], inputs: vec![NodeInput::network(generic!(T), 1)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
..Default::default() ..Default::default()
}, },
// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with. // Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)], inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
..Default::default() ..Default::default()
}, },
// The monitor node is used to display a thumbnail in the UI // The monitor node is used to display a thumbnail in the UI
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(generic!(T)), call_argument: generic!(T),
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(3), 0)], inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(3), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
..Default::default() ..Default::default()
@@ -349,7 +349,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias // Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
DocumentNode { DocumentNode {
manual_composition: Some(generic!(T)), call_argument: generic!(T),
implementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER),
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(TaggedValue), 1), NodeInput::network(concrete!(TaggedValue), 1),
@@ -365,7 +365,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)], inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
..Default::default() ..Default::default()
}, },
// The monitor node is used to display a thumbnail in the UI. // The monitor node is used to display a thumbnail in the UI.
@@ -373,12 +373,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
inputs: vec![ inputs: vec![
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0), NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(2), 0),
@@ -495,13 +495,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)], inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER),
..Default::default() ..Default::default()
}, },
@@ -568,7 +568,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
..Default::default() ..Default::default()
@@ -630,20 +630,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::scope("editor-api")], inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER),
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
..Default::default() ..Default::default()
}, },
] ]
@@ -716,7 +716,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Raster: Pattern", category: "Raster: Pattern",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::None, false),
@@ -783,7 +783,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
@@ -792,7 +792,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
@@ -801,7 +801,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
@@ -810,7 +810,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
] ]
@@ -888,13 +888,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)], inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)], inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
] ]
@@ -962,7 +962,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1), NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
NodeInput::network(concrete!(BrushCache), 2), NodeInput::network(concrete!(BrushCache), 2),
], ],
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER),
..Default::default() ..Default::default()
}] }]
@@ -1013,7 +1013,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)], inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -1032,7 +1032,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)], inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -1054,13 +1054,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
inputs: vec![NodeInput::scope("editor-api")], inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
..Default::default() ..Default::default()
@@ -1126,12 +1126,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)],
manual_composition: Some(generic!(T)), call_argument: generic!(T),
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(generic!(T)), call_argument: generic!(T),
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
..Default::default() ..Default::default()
@@ -1247,7 +1247,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)], inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
@@ -1257,7 +1257,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1), NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
], ],
manual_composition: Some(generic!(T)), call_argument: generic!(T),
implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER),
..Default::default() ..Default::default()
}, },
@@ -1317,7 +1317,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER),
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
inputs: vec![ inputs: vec![
NodeInput::scope("editor-api"), NodeInput::scope("editor-api"),
NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false), NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false),
@@ -1427,7 +1427,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)], inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
@@ -1439,7 +1439,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(DVec2), 3), NodeInput::network(concrete!(DVec2), 3),
NodeInput::network(concrete!(DVec2), 4), NodeInput::network(concrete!(DVec2), 4),
], ],
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
..Default::default() ..Default::default()
}, },
@@ -1524,25 +1524,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
] ]
@@ -1622,7 +1622,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)], inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
@@ -1637,25 +1637,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(0), 0),
], ],
implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(3), 0)], inputs: vec![NodeInput::node(NodeId(3), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
] ]
@@ -1791,26 +1791,26 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(f64), 1), NodeInput::network(concrete!(f64), 1),
NodeInput::network(concrete!(u32), 2), NodeInput::network(concrete!(u32), 2),
], ],
manual_composition: Some(generic!(T)), call_argument: generic!(T),
implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), call_argument: generic!(T),
..Default::default() ..Default::default()
}, },
] ]
@@ -1944,10 +1944,10 @@ fn static_input_properties() -> InputProperties {
"string".to_string(), "string".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let Some(value) = context.network_interface.input_data(&node_id, index, "string_properties", context.selection_network_path) else { let Some(value) = context.network_interface.input_data(&node_id, index, "string_properties", context.selection_network_path) else {
return Err(format!("Could not get string properties for node {}", node_id)); return Err(format!("Could not get string properties for node {node_id}"));
}; };
let Some(string) = value.as_str() else { let Some(string) = value.as_str() else {
return Err(format!("Could not downcast string properties for node {}", node_id)); return Err(format!("Could not downcast string properties for node {node_id}"));
}; };
Ok(node_properties::string_properties(string)) Ok(node_properties::string_properties(string))
}), }),
@@ -59,8 +59,8 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
inputs, inputs,
manual_composition: Some(input_type.clone()), call_argument: (input_type.clone()),
implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()), implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
visible: true, visible: true,
skip_deduplication: false, skip_deduplication: false,
..Default::default() ..Default::default()
@@ -1516,7 +1516,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let mut nodes = Vec::new(); let mut nodes = Vec::new();
for node_id in &self.frontend_nodes { for node_id in &self.frontend_nodes {
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else { 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); log::error!("Could not get bbox for node: {node_id:?}");
continue; continue;
}; };
@@ -1721,7 +1721,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
} }
NodeGraphMessage::ToggleLocked { node_id } => { NodeGraphMessage::ToggleLocked { node_id } => {
let Some(node_metadata) = network_interface.document_network_metadata().persistent_metadata.node_metadata.get(&node_id) else { let Some(node_metadata) = network_interface.document_network_metadata().persistent_metadata.node_metadata.get(&node_id) else {
log::error!("Cannot get node {:?} in NodeGraphMessage::ToggleLocked", node_id); log::error!("Cannot get node {node_id:?} in NodeGraphMessage::ToggleLocked");
return; return;
}; };
@@ -2486,7 +2486,7 @@ impl NodeGraphMessageHandler {
data_type: frontend_data_type, data_type: frontend_data_type,
name: "Output 1".to_string(), name: "Output 1".to_string(),
description: String::new(), description: String::new(),
resolved_type: format!("{:?}", output_type), resolved_type: format!("{output_type:?}"),
connected_to, connected_to,
}) })
} else { } else {
@@ -2518,7 +2518,7 @@ impl NodeGraphMessageHandler {
data_type, data_type,
name: output_name, name: output_name,
description: String::new(), description: String::new(),
resolved_type: format!("{:?}", output_type), resolved_type: format!("{output_type:?}"),
connected_to, connected_to,
}); });
} }
@@ -1156,7 +1156,7 @@ impl OverlayContextInternal {
let move_to = last_point != Some(start_id); let move_to = last_point != Some(start_id);
last_point = Some(end_id); last_point = Some(end_id);
self.bezier_to_path(bezier, row.transform.clone(), move_to, &mut path); self.bezier_to_path(bezier, *row.transform, move_to, &mut path);
} }
// Render the path // Render the path
@@ -25,6 +25,7 @@ use kurbo::BezPath;
use serde_json::{Value, json}; use serde_json::{Value, json};
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
use std::hash::{DefaultHasher, Hash, Hasher}; use std::hash::{DefaultHasher, Hash, Hasher};
use std::ops::Deref;
/// All network modifications should be done through this API, so the fields cannot be public. However, all fields within this struct can be public since it it not possible to have a public mutable reference. /// All network modifications should be done through this API, so the fields cannot be public. However, all fields within this struct can be public since it it not possible to have a public mutable reference.
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] #[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
@@ -73,10 +74,8 @@ impl NodeNetworkInterface {
fix_network(network); fix_network(network);
} }
if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation { if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation {
if protonode.name.contains("PathModifyNode") { if protonode.name.contains("PathModifyNode") && node.inputs.len() < 3 {
if node.inputs.len() < 3 { node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath));
node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath));
}
} }
} }
} }
@@ -460,13 +459,9 @@ impl NodeNetworkInterface {
/// If the node is not in the hashmap then a default input is found based on the compiled network, using the node_id passed as a parameter /// If the node is not in the hashmap then a default input is found based on the compiled network, using the node_id passed as a parameter
pub fn map_ids(&mut self, mut node_template: NodeTemplate, node_id: &NodeId, new_ids: &HashMap<NodeId, NodeId>, network_path: &[NodeId]) -> NodeTemplate { pub fn map_ids(&mut self, mut node_template: NodeTemplate, node_id: &NodeId, new_ids: &HashMap<NodeId, NodeId>, network_path: &[NodeId]) -> NodeTemplate {
for (input_index, input) in node_template.document_node.inputs.iter_mut().enumerate() { for (input_index, input) in node_template.document_node.inputs.iter_mut().enumerate() {
if let &mut NodeInput::Node { node_id: id, output_index, lambda } = input { if let &mut NodeInput::Node { node_id: id, output_index } = input {
if let Some(&new_id) = new_ids.get(&id) { if let Some(&new_id) = new_ids.get(&id) {
*input = NodeInput::Node { *input = NodeInput::Node { node_id: new_id, output_index };
node_id: new_id,
output_index,
lambda,
};
} else { } else {
// Disconnect node input if it is not connected to another node in new_ids // Disconnect node input if it is not connected to another node in new_ids
let tagged_value = TaggedValue::from_type_or_none(&self.input_type(&InputConnector::node(*node_id, input_index), network_path).0); let tagged_value = TaggedValue::from_type_or_none(&self.input_type(&InputConnector::node(*node_id, input_index), network_path).0);
@@ -547,12 +542,11 @@ impl NodeNetworkInterface {
} }
} }
DocumentNodeImplementation::ProtoNode(_) => { DocumentNodeImplementation::ProtoNode(_) => {
// If a node has manual composition, then offset the input index by 1 since the proto node also includes the type of the input passed through manual composition. // Offset the input index by 1 since the proto node also includes the type of the input passed as a call argument.
let manual_composition_offset = if node.manual_composition.is_some() { 1 } else { 0 };
self.resolved_types self.resolved_types
.types .types
.get(node_id_path.as_slice()) .get(node_id_path.as_slice())
.and_then(|node_types| node_types.inputs.get(input_index + manual_composition_offset).cloned()) .and_then(|node_types| node_types.inputs.get(input_index + 1).cloned())
.map(|node_types| (node_types, TypeSource::Compiled)) .map(|node_types| (node_types, TypeSource::Compiled))
} }
DocumentNodeImplementation::Extract => None, DocumentNodeImplementation::Extract => None,
@@ -581,7 +575,7 @@ impl NodeNetworkInterface {
return (concrete!(()), TypeSource::Error("could not resolve protonode")); return (concrete!(()), TypeSource::Error("could not resolve protonode"));
}; };
let skip_footprint = if node.manual_composition.is_some() { 1 } else { 0 }; let skip_footprint = 1;
let Some(input_type) = std::iter::once(node_types.call_argument.clone()).chain(node_types.inputs.clone()).nth(input_index + skip_footprint) else { let Some(input_type) = std::iter::once(node_types.call_argument.clone()).chain(node_types.inputs.clone()).nth(input_index + skip_footprint) else {
log::error!("Could not get type"); log::error!("Could not get type");
@@ -821,7 +815,7 @@ impl NodeNetworkInterface {
data_type, data_type,
name, name,
description, description,
resolved_type: format!("{:?}", input_type), resolved_type: format!("{input_type:?}"),
connected_to, connected_to,
}, },
click_target, click_target,
@@ -1069,7 +1063,7 @@ impl NodeNetworkInterface {
pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&Option<String>> { pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&Option<String>> {
let Some(node_metadata) = self.node_metadata(node_id, network_path) else { let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
log::error!("Could not get reference for node: {:?}", node_id); log::error!("Could not get reference for node: {node_id:?}");
return None; return None;
}; };
Some(&node_metadata.persistent_metadata.reference) Some(&node_metadata.persistent_metadata.reference)
@@ -1287,7 +1281,7 @@ impl NodeNetworkInterface {
let artboard = self.document_node(&artboard_node_identifier.to_node(), &[]); let artboard = self.document_node(&artboard_node_identifier.to_node(), &[]);
let clip_input = artboard.unwrap().inputs.get(5).unwrap(); let clip_input = artboard.unwrap().inputs.get(5).unwrap();
if let NodeInput::Value { tagged_value, .. } = clip_input { if let NodeInput::Value { tagged_value, .. } = clip_input {
if tagged_value.clone().into_inner() == TaggedValue::Bool(true) { if tagged_value.clone().deref() == &TaggedValue::Bool(true) {
return Some(Quad::clip( return Some(Quad::clip(
self.document_metadata.bounding_box_document(layer).unwrap_or_default(), self.document_metadata.bounding_box_document(layer).unwrap_or_default(),
self.document_metadata.bounding_box_document(artboard_node_identifier).unwrap_or_default(), self.document_metadata.bounding_box_document(artboard_node_identifier).unwrap_or_default(),
@@ -1499,7 +1493,7 @@ impl NodeNetworkInterface {
let mut node_metadata = DocumentNodeMetadata::default(); let mut node_metadata = DocumentNodeMetadata::default();
node.inputs = old_node.inputs; node.inputs = old_node.inputs;
node.manual_composition = old_node.manual_composition; node.call_argument = old_node.manual_composition.unwrap();
node.visible = old_node.visible; node.visible = old_node.visible;
node.skip_deduplication = old_node.skip_deduplication; node.skip_deduplication = old_node.skip_deduplication;
node.original_location = old_node.original_location; node.original_location = old_node.original_location;
@@ -2522,7 +2516,7 @@ impl NodeNetworkInterface {
InputConnector::Node { node_id, input_index } => { InputConnector::Node { node_id, input_index } => {
let input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?; let input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?;
let TransientMetadata::Loaded(wire) = &input_metadata.wire else { let TransientMetadata::Loaded(wire) = &input_metadata.wire else {
log::error!("Could not load wire for input: {:?}", input); log::error!("Could not load wire for input: {input:?}");
return None; return None;
}; };
wire.clone() wire.clone()
@@ -2530,7 +2524,7 @@ impl NodeNetworkInterface {
InputConnector::Export(export_index) => { InputConnector::Export(export_index) => {
let network_metadata = self.network_metadata(network_path)?; let network_metadata = self.network_metadata(network_path)?;
let Some(TransientMetadata::Loaded(wire)) = network_metadata.transient_metadata.wires.get(*export_index) else { let Some(TransientMetadata::Loaded(wire)) = network_metadata.transient_metadata.wires.get(*export_index) else {
log::error!("Could not load wire for input: {:?}", input); log::error!("Could not load wire for input: {input:?}");
return None; return None;
}; };
wire.clone() wire.clone()
@@ -2701,12 +2695,12 @@ impl NodeNetworkInterface {
return None; return None;
} }
let Some(input_position) = self.get_input_center(&input, network_path) else { let Some(input_position) = self.get_input_center(&input, network_path) else {
log::error!("Could not get dom rect for wire end in root node: {:?}", input); log::error!("Could not get dom rect for wire end in root node: {input:?}");
return None; return None;
}; };
let upstream_output = OutputConnector::node(root_node.node_id, root_node.output_index); let upstream_output = OutputConnector::node(root_node.node_id, root_node.output_index);
let Some(output_position) = self.get_output_center(&upstream_output, network_path) else { let Some(output_position) = self.get_output_center(&upstream_output, network_path) else {
log::error!("Could not get dom rect for wire start in root node: {:?}", upstream_output); log::error!("Could not get dom rect for wire start in root node: {upstream_output:?}");
return None; return None;
}; };
let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0); let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0);
@@ -2733,7 +2727,7 @@ impl NodeNetworkInterface {
/// Returns the vector subpath and a boolean of whether the wire should be thick. /// 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<(BezPath, bool)> { pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(BezPath, bool)> {
let Some(input_position) = self.get_input_center(input, network_path) else { let Some(input_position) = self.get_input_center(input, network_path) else {
log::error!("Could not get dom rect for wire end: {:?}", input); log::error!("Could not get dom rect for wire end: {input:?}");
return None; 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 // An upstream output could not be found, so the wire does not exist, but it should still be loaded as as empty vector
@@ -2741,7 +2735,7 @@ impl NodeNetworkInterface {
return Some((BezPath::new(), false)); return Some((BezPath::new(), false));
}; };
let Some(output_position) = self.get_output_center(&upstream_output, network_path) else { 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); log::error!("Could not get dom rect for wire start: {upstream_output:?}");
return None; return None;
}; };
let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0); let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0);
@@ -3357,7 +3351,7 @@ impl NodeNetworkInterface {
self.selected_nodes() self.selected_nodes()
.0 .0
.iter() .iter()
.filter(|node| self.is_layer(&node, &[])) .filter(|node| self.is_layer(node, &[]))
.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self))) .filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))
.reduce(Quad::combine_bounds) .reduce(Quad::combine_bounds)
} }
@@ -3366,7 +3360,7 @@ impl NodeNetworkInterface {
self.selected_nodes() self.selected_nodes()
.0 .0
.iter() .iter()
.filter(|node| self.is_layer(&node, &[]) && !self.is_locked(&node, &[])) .filter(|node| self.is_layer(node, &[]) && !self.is_locked(node, &[]))
.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self))) .filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))
.reduce(Quad::combine_bounds) .reduce(Quad::combine_bounds)
} }
@@ -4138,7 +4132,7 @@ impl NodeNetworkInterface {
if let DocumentNodeImplementation::Network(network) = &node.implementation { if let DocumentNodeImplementation::Network(network) = &node.implementation {
let number_of_exports = network.exports.len(); let number_of_exports = network.exports.len();
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { let Some(metadata) = self.node_metadata_mut(node_id, network_path) else {
log::error!("Could not get metadata for node: {:?}", node_id); log::error!("Could not get metadata for node: {node_id:?}");
return; return;
}; };
metadata.persistent_metadata.output_names.resize(number_of_exports, "".to_string()); metadata.persistent_metadata.output_names.resize(number_of_exports, "".to_string());
@@ -4155,7 +4149,7 @@ impl NodeNetworkInterface {
} }
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
pub fn set_manual_compostion(&mut self, node_id: &NodeId, network_path: &[NodeId], manual_composition: Option<Type>) { pub fn set_call_argument(&mut self, node_id: &NodeId, network_path: &[NodeId], call_argument: Type) {
let Some(network) = self.network_mut(network_path) else { let Some(network) = self.network_mut(network_path) else {
log::error!("Could not get nested network in set_implementation"); log::error!("Could not get nested network in set_implementation");
return; return;
@@ -4164,7 +4158,7 @@ impl NodeNetworkInterface {
log::error!("Could not get node in set_implementation"); log::error!("Could not get node in set_implementation");
return; return;
}; };
node.manual_composition = manual_composition; node.call_argument = call_argument;
} }
pub fn set_input(&mut self, input_connector: &InputConnector, new_input: NodeInput, network_path: &[NodeId]) { pub fn set_input(&mut self, input_connector: &InputConnector, new_input: NodeInput, network_path: &[NodeId]) {
@@ -20,6 +20,7 @@ use std::collections::HashMap;
const TEXT_REPLACEMENTS: &[(&str, &str)] = &[ const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePolylineNode"), ("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePolylineNode"),
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"), ("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
("\"manual_composition\":null", "\"manual_composition\":{\"Generic\":\"T\"}"),
]; ];
pub struct NodeReplacement<'a> { pub struct NodeReplacement<'a> {
@@ -551,7 +552,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
let mut default_template = NodeTemplate::default(); let mut default_template = NodeTemplate::default();
default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone()); default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone());
document.network_interface.replace_implementation(node_id, &network_path, &mut default_template); document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into()))); document.network_interface.set_call_argument(node_id, &network_path, graph_craft::Type::Generic("T".into()));
} }
} }
} }
@@ -576,11 +577,11 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
} }
} }
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition // Upgrade old nodes to use `Context` instead of `()` or `Footprint` as their call argument
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { if node.call_argument == graph_craft::concrete!(()) || node.call_argument == graph_craft::concrete!(graphene_std::transform::Footprint) {
document document
.network_interface .network_interface
.set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); .set_call_argument(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into());
} }
// Only nodes that have not been modified and still refer to a definition can be updated // Only nodes that have not been modified and still refer to a definition can be updated
@@ -1083,8 +1084,8 @@ mod tests {
*hashmap.entry(node.node.clone()).or_default() += 1; *hashmap.entry(node.node.clone()).or_default() += 1;
}); });
let duplicates = hashmap.iter().filter(|(_, count)| **count > 1).map(|(node, _)| &node.name).collect::<Vec<_>>(); let duplicates = hashmap.iter().filter(|(_, count)| **count > 1).map(|(node, _)| &node.name).collect::<Vec<_>>();
if duplicates.len() > 0 { if !duplicates.is_empty() {
panic!("Duplicate entries in `NODE_REPLACEMENTS`: {:?}", duplicates); panic!("Duplicate entries in `NODE_REPLACEMENTS`: {duplicates:?}");
} }
} }
} }
@@ -101,14 +101,9 @@ impl Circle {
}; };
let dimensions = (start - end).abs(); let dimensions = (start - end).abs();
let radius: f64;
// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly // We keep the smaller dimension's scale at 1 and scale the other dimension accordingly
if dimensions.x > dimensions.y { let radius: f64 = if dimensions.x > dimensions.y { dimensions.y / 2. } else { dimensions.x / 2. };
radius = dimensions.y / 2.;
} else {
radius = dimensions.x / 2.;
}
responses.add(NodeGraphMessage::SetInput { responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1), input_connector: InputConnector::node(node_id, 1),
@@ -210,18 +210,18 @@ mod test_line_tool {
async fn get_line_node_inputs(editor: &mut EditorTestUtils) -> Option<(DVec2, DVec2)> { async fn get_line_node_inputs(editor: &mut EditorTestUtils) -> Option<(DVec2, DVec2)> {
let document = editor.active_document(); let document = editor.active_document();
let network_interface = &document.network_interface; let network_interface = &document.network_interface;
let node_id = network_interface
network_interface
.selected_nodes() .selected_nodes()
.selected_visible_and_unlocked_layers(network_interface) .selected_visible_and_unlocked_layers(network_interface)
.filter_map(|layer| { .filter_map(|layer| {
let node_inputs = NodeGraphLayer::new(layer, &network_interface).find_node_inputs("Line")?; let node_inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Line")?;
let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else { let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else {
return None; return None;
}; };
Some((start, end)) Some((start, end))
}) })
.next(); .next()
node_id
} }
#[tokio::test] #[tokio::test]
@@ -245,11 +245,7 @@ mod test_line_tool {
editor.new_document().await; editor.new_document().await;
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await; editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await;
editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 50.) }).await; editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 50.) }).await;
editor editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30_f64.to_radians() }).await;
.handle_message(NavigationMessage::CanvasTiltSet {
angle_radians: (30. as f64).to_radians(),
})
.await;
editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await; editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await;
if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await {
let document = editor.active_document(); let document = editor.active_document();
@@ -261,15 +257,11 @@ mod test_line_tool {
assert!( assert!(
(start_input - expected_start).length() < 1., (start_input - expected_start).length() < 1.,
"Start point should match expected document coordinates. Got {:?}, expected {:?}", "Start point should match expected document coordinates. Got {start_input:?}, expected {expected_start:?}"
start_input,
expected_start
); );
assert!( assert!(
(end_input - expected_end).length() < 1., (end_input - expected_end).length() < 1.,
"End point should match expected document coordinates. Got {:?}, expected {:?}", "End point should match expected document coordinates. Got {end_input:?}, expected {expected_end:?}"
end_input,
expected_end
); );
} else { } else {
panic!("Line was not created successfully with transformed viewport"); panic!("Line was not created successfully with transformed viewport");
@@ -282,27 +274,19 @@ mod test_line_tool {
editor.new_document().await; editor.new_document().await;
editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::CONTROL).await; editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::CONTROL).await;
if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await {
match (start_input, end_input) { let line_vec = end_input - start_input;
(start_input, end_input) => { let original_angle = line_vec.angle_to(DVec2::X);
let line_vec = end_input - start_input; editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await;
let original_angle = line_vec.angle_to(DVec2::X); if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await {
editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await; let updated_line_vec = updated_end - updated_start;
if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await { let updated_angle = updated_line_vec.angle_to(DVec2::X);
match (updated_start, updated_end) { print!("{original_angle:?}");
(updated_start, updated_end) => { print!("{updated_angle:?}");
let updated_line_vec = updated_end - updated_start; assert!(
let updated_angle = updated_line_vec.angle_to(DVec2::X); line_vec.normalize().dot(updated_line_vec.normalize()).abs() - 1. < 1e-6,
print!("{:?}", original_angle); "Line angle should be locked when Ctrl is kept pressed"
print!("{:?}", updated_angle); );
assert!( assert!((updated_start - updated_end).length() > 1., "Line should be able to change length when Ctrl is kept pressed");
line_vec.normalize().dot(updated_line_vec.normalize()).abs() - 1. < 1e-6,
"Line angle should be locked when Ctrl is kept pressed"
);
assert!((updated_start - updated_end).length() > 1., "Line should be able to change length when Ctrl is kept pressed");
}
}
}
}
} }
} }
} }
@@ -313,14 +297,10 @@ mod test_line_tool {
editor.new_document().await; editor.new_document().await;
editor.drag_tool(ToolType::Line, 100., 100., 200., 100., ModifierKeys::ALT).await; editor.drag_tool(ToolType::Line, 100., 100., 200., 100., ModifierKeys::ALT).await;
if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await { if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await {
match (start_input, end_input) { let expected_start = DVec2::new(0., 100.);
(start_input, end_input) => { let expected_end = DVec2::new(200., 100.);
let expected_start = DVec2::new(0., 100.); assert!((start_input - expected_start).length() < 1., "Start point should be near (0, 100)");
let expected_end = DVec2::new(200., 100.); assert!((end_input - expected_end).length() < 1., "End point should be near (200, 100)");
assert!((start_input - expected_start).length() < 1., "Start point should be near (0, 100)");
assert!((end_input - expected_end).length() < 1., "End point should be near (200, 100)");
}
}
} }
} }
@@ -569,7 +569,7 @@ mod test_artboard {
async fn get_artboards(editor: &mut EditorTestUtils) -> Table<graphene_std::Artboard> { async fn get_artboards(editor: &mut EditorTestUtils) -> Table<graphene_std::Artboard> {
let instrumented = match editor.eval_graph().await { let instrumented = match editor.eval_graph().await {
Ok(instrumented) => instrumented, Ok(instrumented) => instrumented,
Err(e) => panic!("Failed to evaluate graph: {}", e), Err(e) => panic!("Failed to evaluate graph: {e}"),
}; };
instrumented instrumented
.grab_all_input::<graphene_std::graphic::extend::NewInput<graphene_std::Artboard>>(&editor.runtime) .grab_all_input::<graphene_std::graphic::extend::NewInput<graphene_std::Artboard>>(&editor.runtime)
@@ -416,7 +416,10 @@ mod test_freehand {
fn verify_path_points(vector_and_transform_list: &[(Vector, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> { fn verify_path_points(vector_and_transform_list: &[(Vector, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> {
assert_eq!(vector_and_transform_list.len(), 1, "There should be one row of Vector geometry"); assert_eq!(vector_and_transform_list.len(), 1, "There should be one row of Vector geometry");
let (vector, transform) = vector_and_transform_list.iter().find(|(data, _)| data.point_domain.ids().len() > 0).ok_or("Could not find path data")?; let (vector, transform) = vector_and_transform_list
.iter()
.find(|(data, _)| !data.point_domain.ids().is_empty())
.ok_or("Could not find path data")?;
let point_count = vector.point_domain.ids().len(); let point_count = vector.point_domain.ids().len();
let segment_count = vector.segment_domain.ids().len(); let segment_count = vector.segment_domain.ids().len();
@@ -424,7 +427,7 @@ mod test_freehand {
let actual_positions: Vec<DVec2> = vector.point_domain.positions().iter().map(|&position| transform.transform_point2(position)).collect(); let actual_positions: Vec<DVec2> = vector.point_domain.positions().iter().map(|&position| transform.transform_point2(position)).collect();
if segment_count != point_count - 1 { if segment_count != point_count - 1 {
return Err(format!("Expected segments to be one less than points, got {} segments for {} points", segment_count, point_count)); return Err(format!("Expected segments to be one less than points, got {segment_count} segments for {point_count} points"));
} }
if point_count != expected_captured_points.len() { if point_count != expected_captured_points.len() {
@@ -434,7 +437,7 @@ mod test_freehand {
for (i, (&expected, &actual)) in expected_captured_points.iter().zip(actual_positions.iter()).enumerate() { for (i, (&expected, &actual)) in expected_captured_points.iter().zip(actual_positions.iter()).enumerate() {
let distance = (expected - actual).length(); let distance = (expected - actual).length();
if distance >= tolerance { if distance >= tolerance {
return Err(format!("Point {} position mismatch: expected {:?}, got {:?} (distance: {})", i, expected, actual, distance)); return Err(format!("Point {i} position mismatch: expected {expected:?}, got {actual:?} (distance: {distance})"));
} }
} }
@@ -508,7 +511,7 @@ mod test_freehand {
let initial_point_count = initial_vector.point_domain.ids().len(); let initial_point_count = initial_vector.point_domain.ids().len();
let initial_segment_count = initial_vector.segment_domain.ids().len(); let initial_segment_count = initial_vector.segment_domain.ids().len();
assert!(initial_point_count >= 2, "Expected at least 2 points in initial path, found {}", initial_point_count); assert!(initial_point_count >= 2, "Expected at least 2 points in initial path, found {initial_point_count}");
assert_eq!( assert_eq!(
initial_segment_count, initial_segment_count,
initial_point_count - 1, initial_point_count - 1,
@@ -569,17 +572,13 @@ mod test_freehand {
assert!( assert!(
extended_point_count > initial_point_count, extended_point_count > initial_point_count,
"Expected more points after extension, initial: {}, after extension: {}", "Expected more points after extension, initial: {initial_point_count}, after extension: {extended_point_count}"
initial_point_count,
extended_point_count
); );
assert_eq!( assert_eq!(
extended_segment_count, extended_segment_count,
extended_point_count - 1, extended_point_count - 1,
"Expected segments to be one less than points, points: {}, segments: {}", "Expected segments to be one less than points, points: {extended_point_count}, segments: {extended_segment_count}"
extended_point_count,
extended_segment_count
); );
let layer_count = { let layer_count = {
@@ -627,8 +626,8 @@ mod test_freehand {
let existing_layer_id = { let existing_layer_id = {
let document = editor.active_document(); let document = editor.active_document();
let layer = document.metadata().all_layers().next().unwrap();
layer document.metadata().all_layers().next().unwrap()
}; };
editor editor
@@ -685,9 +684,7 @@ mod test_freehand {
assert!( assert!(
final_point_count > initial_point_count, final_point_count > initial_point_count,
"Expected more points after appending to layer, initial: {}, after append: {}", "Expected more points after appending to layer, initial: {initial_point_count}, after append: {final_point_count}"
initial_point_count,
final_point_count
); );
let expected_new_points = second_path_points.len(); let expected_new_points = second_path_points.len();
@@ -552,7 +552,7 @@ mod test_gradient {
async fn get_fills(editor: &mut EditorTestUtils) -> Vec<(Fill, DAffine2)> { async fn get_fills(editor: &mut EditorTestUtils) -> Vec<(Fill, DAffine2)> {
let instrumented = match editor.eval_graph().await { let instrumented = match editor.eval_graph().await {
Ok(instrumented) => instrumented, Ok(instrumented) => instrumented,
Err(e) => panic!("Failed to evaluate graph: {}", e), Err(e) => panic!("Failed to evaluate graph: {e}"),
}; };
let document = editor.active_document(); let document = editor.active_document();
@@ -573,7 +573,7 @@ mod test_gradient {
let (fill, transform) = fills.first().unwrap(); let (fill, transform) = fills.first().unwrap();
let gradient = fill.as_gradient().expect("Expected gradient fill type"); let gradient = fill.as_gradient().expect("Expected gradient fill type");
(gradient.clone(), transform.clone()) (gradient.clone(), *transform)
} }
fn assert_stops_at_positions(actual_positions: &[f64], expected_positions: &[f64], tolerance: f64) { fn assert_stops_at_positions(actual_positions: &[f64], expected_positions: &[f64], tolerance: f64) {
@@ -586,7 +586,7 @@ mod test_gradient {
); );
for (i, (actual, expected)) in actual_positions.iter().zip(expected_positions.iter()).enumerate() { for (i, (actual, expected)) in actual_positions.iter().zip(expected_positions.iter()).enumerate() {
assert!((actual - expected).abs() < tolerance, "Stop {}: Expected position near {}, got {}", i, expected, actual); assert!((actual - expected).abs() < tolerance, "Stop {i}: Expected position near {expected}, got {actual}");
} }
} }
@@ -713,8 +713,7 @@ mod test_gradient {
let positions: Vec<f64> = updated_gradient.stops.iter().map(|(pos, _)| *pos).collect(); let positions: Vec<f64> = updated_gradient.stops.iter().map(|(pos, _)| *pos).collect();
assert!( assert!(
positions.iter().any(|pos| (pos - 0.5).abs() < 0.1), positions.iter().any(|pos| (pos - 0.5).abs() < 0.1),
"Expected to find a stop near position 0.5, but found: {:?}", "Expected to find a stop near position 0.5, but found: {positions:?}"
positions
); );
} }
@@ -782,7 +781,7 @@ mod test_gradient {
// Verify the end point has been updated to the new position // Verify the end point has been updated to the new position
let updated_end = transform.transform_point2(updated_gradient.end); let updated_end = transform.transform_point2(updated_gradient.end);
assert!(updated_end.abs_diff_eq(DVec2::new(100., 50.), 1e-10), "Expected end point at (100, 50), got {:?}", updated_end); assert!(updated_end.abs_diff_eq(DVec2::new(100., 50.), 1e-10), "Expected end point at (100, 50), got {updated_end:?}");
} }
#[tokio::test] #[tokio::test]
@@ -202,11 +202,11 @@ impl SelectTool {
let list = <BooleanOperation as graphene_std::choice_type::ChoiceTypeStatic>::list(); let list = <BooleanOperation as graphene_std::choice_type::ChoiceTypeStatic>::list();
list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| { list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
let mut tooltip = info.label.to_string(); let mut tooltip = info.label.to_string();
if let Some(doc) = info.docstring.as_deref() { if let Some(doc) = info.docstring {
tooltip.push_str("\n\n"); tooltip.push_str("\n\n");
tooltip.push_str(doc); tooltip.push_str(doc);
} }
IconButton::new(info.icon.as_deref().unwrap(), 24) IconButton::new(info.icon.unwrap(), 24)
.tooltip(tooltip) .tooltip(tooltip)
.disabled(selected_count == 0) .disabled(selected_count == 0)
.on_update(move |_| { .on_update(move |_| {
@@ -852,7 +852,13 @@ impl Fsm for SelectToolFsmState {
if let Some(pivot) = pivot { if let Some(pivot) = pivot {
let offset = tool_data let offset = tool_data
.pivot_gizmo_start .pivot_gizmo_start
.map(|offset| tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - offset).unwrap_or_default()) .map(|offset| {
if tool_data.pivot_gizmo.pivot_disconnected() {
tool_data.drag_current - offset
} else {
Default::default()
}
})
.unwrap_or_default(); .unwrap_or_default();
let shift = tool_data.pivot_gizmo_shift.unwrap_or_default(); let shift = tool_data.pivot_gizmo_shift.unwrap_or_default();
overlay_context.pivot(pivot + offset + shift, angle); overlay_context.pivot(pivot + offset + shift, angle);
@@ -895,7 +901,7 @@ impl Fsm for SelectToolFsmState {
color color
} else { } else {
let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb(); let color_string = &graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb();
&format!("#{}", color_string) &format!("#{color_string}")
}; };
let line_center = tool_data.line_center; let line_center = tool_data.line_center;
overlay_context.line(line_center - direction * viewport_diagonal, line_center + direction * viewport_diagonal, Some(color), None); overlay_context.line(line_center - direction * viewport_diagonal, line_center + direction * viewport_diagonal, Some(color), None);
@@ -1455,7 +1461,11 @@ impl Fsm for SelectToolFsmState {
tool_data.select_single_layer = None; tool_data.select_single_layer = None;
if let Some(start) = tool_data.pivot_gizmo_start { if let Some(start) = tool_data.pivot_gizmo_start {
let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default(); let offset = if tool_data.pivot_gizmo.pivot_disconnected() {
tool_data.drag_current - start
} else {
Default::default()
};
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() { if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
*v += offset; *v += offset;
} }
@@ -1649,7 +1659,9 @@ impl Fsm for SelectToolFsmState {
} }
(_, SelectToolMessage::PivotShift { offset, flush }) => { (_, SelectToolMessage::PivotShift { offset, flush }) => {
if flush { if flush {
tool_data.pivot_gizmo.pivot.pivot.as_mut().map(|v| *v += tool_data.pivot_gizmo_shift.take().unwrap_or_default()); if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
*v += tool_data.pivot_gizmo_shift.take().unwrap_or_default();
}
let pivot_gizmo = tool_data.pivot_gizmo(); let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
return self; return self;
@@ -606,11 +606,7 @@ mod test_spline_tool {
assert!( assert!(
distance < epsilon, distance < epsilon,
"Point {} position mismatch: expected {:?}, got {:?} (distance: {})", "Point {i} position mismatch: expected {expected_point:?}, got {actual_point:?} (distance: {distance})"
i,
expected_point,
actual_point,
distance
); );
} }
} }
@@ -644,8 +640,8 @@ mod test_spline_tool {
// Verify initial spline has correct number of points and segments // Verify initial spline has correct number of points and segments
let initial_point_count = first_vector.point_domain.ids().len(); let initial_point_count = first_vector.point_domain.ids().len();
let initial_segment_count = first_vector.segment_domain.ids().len(); let initial_segment_count = first_vector.segment_domain.ids().len();
assert_eq!(initial_point_count, 3, "Expected 3 points in initial spline, found {}", initial_point_count); assert_eq!(initial_point_count, 3, "Expected 3 points in initial spline, found {initial_point_count}");
assert_eq!(initial_segment_count, 2, "Expected 2 segments in initial spline, found {}", initial_segment_count); assert_eq!(initial_segment_count, 2, "Expected 2 segments in initial spline, found {initial_segment_count}");
let layer_to_viewport = document.metadata().transform_to_viewport(spline_layer); let layer_to_viewport = document.metadata().transform_to_viewport(spline_layer);
@@ -679,8 +675,8 @@ mod test_spline_tool {
let extended_point_count = extended_vector.point_domain.ids().len(); let extended_point_count = extended_vector.point_domain.ids().len();
let extended_segment_count = extended_vector.segment_domain.ids().len(); let extended_segment_count = extended_vector.segment_domain.ids().len();
assert_eq!(extended_point_count, 5, "Expected 5 points in extended spline, found {}", extended_point_count); assert_eq!(extended_point_count, 5, "Expected 5 points in extended spline, found {extended_point_count}");
assert_eq!(extended_segment_count, 4, "Expected 4 segments in extended spline, found {}", extended_segment_count); assert_eq!(extended_segment_count, 4, "Expected 4 segments in extended spline, found {extended_segment_count}");
// Verify the spline node is still the same // Verify the spline node is still the same
let extended_spline_node = find_spline(document, spline_layer).expect("Spline node not found after extension"); let extended_spline_node = find_spline(document, spline_layer).expect("Spline node not found after extension");
@@ -715,7 +711,7 @@ mod test_spline_tool {
// Evaluate the graph to ensure everything is processed // Evaluate the graph to ensure everything is processed
if let Err(e) = editor.eval_graph().await { if let Err(e) = editor.eval_graph().await {
panic!("Graph evaluation failed: {}", e); panic!("Graph evaluation failed: {e}");
} }
// Get the layer and vector data // Get the layer and vector data
@@ -755,7 +751,7 @@ mod test_spline_tool {
// Evaluating the graph to ensure everything is processed // Evaluating the graph to ensure everything is processed
if let Err(e) = editor.eval_graph().await { if let Err(e) = editor.eval_graph().await {
panic!("Graph evaluation failed: {}", e); panic!("Graph evaluation failed: {e}");
} }
// Get the layer and vector data // Get the layer and vector data
@@ -793,7 +789,7 @@ mod test_spline_tool {
// Evaluating the graph to ensure everything is processed // Evaluating the graph to ensure everything is processed
if let Err(e) = editor.eval_graph().await { if let Err(e) = editor.eval_graph().await {
panic!("Graph evaluation failed: {}", e); panic!("Graph evaluation failed: {e}");
} }
// Get the layer and vector data // Get the layer and vector data
@@ -832,7 +828,7 @@ mod test_spline_tool {
editor.handle_message(SplineToolMessage::Confirm).await; editor.handle_message(SplineToolMessage::Confirm).await;
if let Err(e) = editor.eval_graph().await { if let Err(e) = editor.eval_graph().await {
panic!("Graph evaluation failed: {}", e); panic!("Graph evaluation failed: {e}");
} }
// Get the layer and vector data // Get the layer and vector data
@@ -889,8 +885,8 @@ mod test_spline_tool {
let point_count = vector.point_domain.ids().len(); let point_count = vector.point_domain.ids().len();
let segment_count = vector.segment_domain.ids().len(); let segment_count = vector.segment_domain.ids().len();
assert_eq!(point_count, 3, "Expected 3 points in the spline, found {}", point_count); assert_eq!(point_count, 3, "Expected 3 points in the spline, found {point_count}");
assert_eq!(segment_count, 2, "Expected 2 segments in the spline, found {}", segment_count); assert_eq!(segment_count, 2, "Expected 2 segments in the spline, found {segment_count}");
let layer_to_viewport = document.metadata().transform_to_viewport(spline_layer); let layer_to_viewport = document.metadata().transform_to_viewport(spline_layer);
@@ -189,7 +189,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
let format_rounded = |value: f64, precision: usize| { let format_rounded = |value: f64, precision: usize| {
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string() format!("{value:.precision$}").trim_end_matches('0').trim_end_matches('.').to_string()
} else { } else {
self.typing.string.clone() self.typing.string.clone()
} }
@@ -892,7 +892,7 @@ mod test_transform_layer {
let final_transform = get_layer_transform(&mut editor, layer).await.unwrap(); let final_transform = get_layer_transform(&mut editor, layer).await.unwrap();
let translation_diff = (final_transform.translation - original_transform.translation).length(); let translation_diff = (final_transform.translation - original_transform.translation).length();
assert!(translation_diff > 10., "Transform should have changed after applying transformation. Diff: {}", translation_diff); assert!(translation_diff > 10., "Transform should have changed after applying transformation. Diff: {translation_diff}");
} }
#[tokio::test] #[tokio::test]
@@ -927,9 +927,7 @@ mod test_transform_layer {
// Verify transform is either restored to original OR reset to identity // Verify transform is either restored to original OR reset to identity
assert!( assert!(
(final_translation - original_translation).length() < 5. || final_translation.length() < 0.001, (final_translation - original_translation).length() < 5. || final_translation.length() < 0.001,
"Transform neither restored to original nor reset to identity. Original: {:?}, Final: {:?}", "Transform neither restored to original nor reset to identity. Original: {original_translation:?}, Final: {final_translation:?}"
original_translation,
final_translation
); );
} }
@@ -958,11 +956,11 @@ mod test_transform_layer {
editor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await; editor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;
let final_transform = get_layer_transform(&mut editor, layer).await.unwrap(); let final_transform = get_layer_transform(&mut editor, layer).await.unwrap();
println!("Final transform: {:?}", final_transform); println!("Final transform: {final_transform:?}");
// Check matrix components have changed (rotation affects matrix2) // Check matrix components have changed (rotation affects matrix2)
let matrix_diff = (final_transform.matrix2.x_axis - original_transform.matrix2.x_axis).length(); let matrix_diff = (final_transform.matrix2.x_axis - original_transform.matrix2.x_axis).length();
assert!(matrix_diff > 0.1, "Rotation should have changed the transform matrix. Diff: {}", matrix_diff); assert!(matrix_diff > 0.1, "Rotation should have changed the transform matrix. Diff: {matrix_diff}");
} }
#[tokio::test] #[tokio::test]
@@ -984,7 +982,7 @@ mod test_transform_layer {
assert!(!after_cancel.translation.y.is_nan(), "Transform is NaN after cancel"); assert!(!after_cancel.translation.y.is_nan(), "Transform is NaN after cancel");
let translation_diff = (after_cancel.translation - original_transform.translation).length(); let translation_diff = (after_cancel.translation - original_transform.translation).length();
assert!(translation_diff < 1., "Translation component changed too much: {}", translation_diff); assert!(translation_diff < 1., "Translation component changed too much: {translation_diff}");
} }
#[tokio::test] #[tokio::test]
@@ -1019,9 +1017,7 @@ mod test_transform_layer {
assert!( assert!(
scale_diff_x > 0.1 || scale_diff_y > 0.1, scale_diff_x > 0.1 || scale_diff_y > 0.1,
"Scaling should have changed the transform matrix. Diffs: x={}, y={}", "Scaling should have changed the transform matrix. Diffs: x={scale_diff_x}, y={scale_diff_y}"
scale_diff_x,
scale_diff_y
); );
} }
@@ -1050,7 +1046,7 @@ mod test_transform_layer {
// Also check translation component is similar // Also check translation component is similar
let translation_diff = (after_cancel.translation - original_transform.translation).length(); let translation_diff = (after_cancel.translation - original_transform.translation).length();
assert!(translation_diff < 1., "Translation component changed too much: {}", translation_diff); assert!(translation_diff < 1., "Translation component changed too much: {translation_diff}");
} }
#[tokio::test] #[tokio::test]
@@ -1077,9 +1073,7 @@ mod test_transform_layer {
let actual_translation = after_grab_transform.translation - original_transform.translation; let actual_translation = after_grab_transform.translation - original_transform.translation;
assert!( assert!(
(actual_translation - expected_translation).length() < 1e-5, (actual_translation - expected_translation).length() < 1e-5,
"Expected translation of {:?}, got {:?}", "Expected translation of {expected_translation:?}, got {actual_translation:?}"
expected_translation,
actual_translation
); );
// 2. Chain to rotation - from current position to create ~45 degree rotation // 2. Chain to rotation - from current position to create ~45 degree rotation
@@ -1115,9 +1109,7 @@ mod test_transform_layer {
let after_scale_det = after_scale_transform.matrix2.determinant(); let after_scale_det = after_scale_transform.matrix2.determinant();
assert!( assert!(
after_scale_det >= 2. * before_scale_det, after_scale_det >= 2. * before_scale_det,
"Scale should increase the determinant of the matrix (before: {}, after: {})", "Scale should increase the determinant of the matrix (before: {before_scale_det}, after: {after_scale_det})"
before_scale_det,
after_scale_det
); );
editor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await; editor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;
@@ -1149,8 +1141,8 @@ mod test_transform_layer {
let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length(); let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();
let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length(); let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();
assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2, got: {}", scale_x); assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2, got: {scale_x}");
assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {}", scale_y); assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {scale_y}");
} }
#[tokio::test] #[tokio::test]
@@ -1175,8 +1167,8 @@ mod test_transform_layer {
let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length(); let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();
let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length(); let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();
assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2, got: {}", scale_x); assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2, got: {scale_x}");
assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {}", scale_y); assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {scale_y}");
} }
#[tokio::test] #[tokio::test]
@@ -1191,11 +1183,7 @@ mod test_transform_layer {
// Rotate the document view (45 degrees) // Rotate the document view (45 degrees)
editor.handle_message(NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }).await; editor.handle_message(NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }).await;
editor editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45_f64.to_radians() }).await;
.handle_message(NavigationMessage::CanvasTiltSet {
angle_radians: (45. as f64).to_radians(),
})
.await;
editor.handle_message(TransformLayerMessage::BeginRotate).await; editor.handle_message(TransformLayerMessage::BeginRotate).await;
editor.handle_message(TransformLayerMessage::TypeDigit { digit: 9 }).await; editor.handle_message(TransformLayerMessage::TypeDigit { digit: 9 }).await;
@@ -1210,7 +1198,7 @@ mod test_transform_layer {
// Normalize angle between 0 and 360 // Normalize angle between 0 and 360
let angle_change = ((angle_change % 360.) + 360.) % 360.; let angle_change = ((angle_change % 360.) + 360.) % 360.;
assert!((angle_change - 90.).abs() < 0.1, "Expected rotation of 90 degrees, got: {}", angle_change); assert!((angle_change - 90.).abs() < 0.1, "Expected rotation of 90 degrees, got: {angle_change}");
} }
#[tokio::test] #[tokio::test]
@@ -1265,8 +1253,8 @@ mod test_transform_layer {
// Verify scale is near zero. // Verify scale is near zero.
let scale_x = near_zero_transform.matrix2.x_axis.length(); let scale_x = near_zero_transform.matrix2.x_axis.length();
let scale_y = near_zero_transform.matrix2.y_axis.length(); let scale_y = near_zero_transform.matrix2.y_axis.length();
assert!(scale_x < 0.001, "Scale factor X should be near zero, got: {}", scale_x); assert!(scale_x < 0.001, "Scale factor X should be near zero, got: {scale_x}");
assert!(scale_y < 0.001, "Scale factor Y should be near zero, got: {}", scale_y); assert!(scale_y < 0.001, "Scale factor Y should be near zero, got: {scale_y}");
assert!(scale_x > 0., "Scale factor X should not be exactly zero"); assert!(scale_x > 0., "Scale factor X should not be exactly zero");
assert!(scale_y > 0., "Scale factor Y should not be exactly zero"); assert!(scale_y > 0., "Scale factor Y should not be exactly zero");
+1 -1
View File
@@ -431,7 +431,7 @@ mod test {
let monitor_node = DocumentNode { let monitor_node = DocumentNode {
inputs: vec![input], inputs: vec![input],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
manual_composition: Some(graph_craft::generic!(T)), call_argument: graph_craft::generic!(T),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}; };
+2 -2
View File
@@ -432,7 +432,7 @@ pub struct InspectResult {
impl InspectResult { impl InspectResult {
pub fn take_data(&mut self) -> Option<Arc<dyn std::any::Any + Send + Sync + 'static>> { pub fn take_data(&mut self) -> Option<Arc<dyn std::any::Any + Send + Sync + 'static>> {
return self.introspected_data.clone(); self.introspected_data.clone()
} }
} }
@@ -462,7 +462,7 @@ impl InspectState {
let monitor_node = DocumentNode { let monitor_node = DocumentNode {
inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
manual_composition: Some(graph_craft::generic!(T)), call_argument: graph_craft::generic!(T),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}; };
+5 -2
View File
@@ -356,10 +356,13 @@ export class UpdatePlatform extends JsMessage {
readonly platform!: AppWindowPlatform; readonly platform!: AppWindowPlatform;
} }
export class UpdateMaximized extends JsMessage { export class UpdateWindowState extends JsMessage {
readonly maximized!: boolean; readonly maximized!: boolean;
readonly minimized!: boolean;
} }
export class CloseWindow extends JsMessage {}
export class UpdateViewportHolePunch extends JsMessage { export class UpdateViewportHolePunch extends JsMessage {
readonly active!: boolean; readonly active!: boolean;
} }
@@ -1741,7 +1744,7 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateLayersPanelControlBarLeftLayout, UpdateLayersPanelControlBarLeftLayout,
UpdateLayersPanelControlBarRightLayout, UpdateLayersPanelControlBarRightLayout,
UpdateLayerWidths, UpdateLayerWidths,
UpdateMaximized, UpdateWindowState,
UpdateMenuBarLayout, UpdateMenuBarLayout,
UpdateMouseCursor, UpdateMouseCursor,
UpdateNodeGraphControlBarLayout, UpdateNodeGraphControlBarLayout,
+3 -3
View File
@@ -3,7 +3,7 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor"; import { type Editor } from "@graphite/editor";
import { type AppWindowPlatform, UpdatePlatform, UpdateMaximized, UpdateViewportHolePunch } from "@graphite/messages"; import { type AppWindowPlatform, UpdatePlatform, UpdateViewportHolePunch, UpdateWindowState } from "@graphite/messages";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createAppWindowState(editor: Editor) { export function createAppWindowState(editor: Editor) {
@@ -20,9 +20,9 @@ export function createAppWindowState(editor: Editor) {
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateMaximized, (maximized) => { editor.subscriptions.subscribeJsMessage(UpdateWindowState, (updateWindowState) => {
update((state) => { update((state) => {
state.maximized = maximized.maximized; state.maximized = updateWindowState.maximized;
return state; return state;
}); });
}); });
+3
View File
@@ -242,6 +242,9 @@ impl EditorHandle {
#[wasm_bindgen(js_name = initAfterFrontendReady)] #[wasm_bindgen(js_name = initAfterFrontendReady)]
pub fn init_after_frontend_ready(&self, platform: String) { pub fn init_after_frontend_ready(&self, platform: String) {
#[cfg(feature = "native")]
crate::native_communcation::initialize_native_communication();
// Send initialization messages // Send initialization messages
let platform = match platform.as_str() { let platform = match platform.as_str() {
"Windows" => Platform::Windows, "Windows" => Platform::Windows,
+11
View File
@@ -20,6 +20,17 @@ pub fn receive_native_message(buffer: ArrayBuffer) {
} }
} }
pub fn initialize_native_communication() {
let global = js_sys::global();
// Get the function by name
let func = js_sys::Reflect::get(&global, &JsValue::from_str("initializeNativeCommunication")).expect("Function not found");
let func = func.dyn_into::<js_sys::Function>().expect("Not a function");
// Call it
func.call0(&JsValue::NULL).expect("Function call failed");
}
pub fn send_message_to_cef(message: String) { pub fn send_message_to_cef(message: String) {
let global = js_sys::global(); let global = js_sys::global();
+2 -3
View File
@@ -53,7 +53,7 @@ pub fn system_desc_derive(input: TokenStream) -> TokenStream {
} }
fn replace_lifetimes(generics: &syn::Generics, replacement: &str) -> Vec<proc_macro2::TokenStream> { fn replace_lifetimes(generics: &syn::Generics, replacement: &str) -> Vec<proc_macro2::TokenStream> {
let params = generics generics
.params .params
.iter() .iter()
.map(|param| { .map(|param| {
@@ -72,6 +72,5 @@ fn replace_lifetimes(generics: &syn::Generics, replacement: &str) -> Vec<proc_ma
}; };
quote! {#param} quote! {#param}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>()
params
} }
+1 -1
View File
@@ -135,7 +135,7 @@ fn parse_lit(mut pairs: Pairs<Rule>) -> Result<(Literal, Unit), ParseError> {
let unit_pairs = unit_pair.into_inner(); // Get the inner pairs for the unit let unit_pairs = unit_pair.into_inner(); // Get the inner pairs for the unit
let (unit, scale) = parse_unit(unit_pairs)?; let (unit, scale) = parse_unit(unit_pairs)?;
println!("found unit: {:?}", unit); println!("found unit: {unit:?}");
Ok(( Ok((
match literal { match literal {
+11 -11
View File
@@ -45,7 +45,7 @@ pub fn commands_from_path_data(d: &str) -> Result<Vec<PathCommand>, BooleanError
*i += cap[0].len(); *i += cap[0].len();
&cap[1] == "1" &cap[1] == "1"
} else { } else {
panic!("Invalid path data. Expected a flag at index {}", i); panic!("Invalid path data. Expected a flag at index {i}");
} }
}; };
@@ -128,8 +128,8 @@ pub fn path_to_path_data(path: &Path, eps: f64) -> String {
path_to_commands(path.iter(), eps) path_to_commands(path.iter(), eps)
.map(|cmd| match cmd { .map(|cmd| match cmd {
PathCommand::Absolute(abs_cmd) => match abs_cmd { PathCommand::Absolute(abs_cmd) => match abs_cmd {
AbsolutePathCommand::H(dx) => format!("H {:.12}", dx), AbsolutePathCommand::H(dx) => format!("H {dx:.12}"),
AbsolutePathCommand::V(dy) => format!("V {:.12}", dy), AbsolutePathCommand::V(dy) => format!("V {dy:.12}"),
AbsolutePathCommand::M(p) => format!("M {:.12},{:.12}", p.x, p.y), AbsolutePathCommand::M(p) => format!("M {:.12},{:.12}", p.x, p.y),
AbsolutePathCommand::L(p) => format!("L {:.12},{:.12}", p.x, p.y), AbsolutePathCommand::L(p) => format!("L {:.12},{:.12}", p.x, p.y),
AbsolutePathCommand::C(p1, p2, p3) => format!("C {:.12},{:.12} {:.12},{:.12} {:.12},{:.12}", p1.x, p1.y, p2.x, p2.y, p3.x, p3.y), AbsolutePathCommand::C(p1, p2, p3) => format!("C {:.12},{:.12} {:.12},{:.12} {:.12},{:.12}", p1.x, p1.y, p2.x, p2.y, p3.x, p3.y),
@@ -146,18 +146,18 @@ pub fn path_to_path_data(path: &Path, eps: f64) -> String {
AbsolutePathCommand::Z => "Z".to_string(), AbsolutePathCommand::Z => "Z".to_string(),
}, },
PathCommand::Relative(rel_cmd) => match rel_cmd { PathCommand::Relative(rel_cmd) => match rel_cmd {
RelativePathCommand::M(dx, dy) => format!("m {:.12},{:.12}", dx, dy), RelativePathCommand::M(dx, dy) => format!("m {dx:.12},{dy:.12}"),
RelativePathCommand::L(dx, dy) => format!("l {:.12},{:.12}", dx, dy), RelativePathCommand::L(dx, dy) => format!("l {dx:.12},{dy:.12}"),
RelativePathCommand::H(dx) => format!("h {:.12}", dx), RelativePathCommand::H(dx) => format!("h {dx:.12}"),
RelativePathCommand::V(dy) => format!("v {:.12}", dy), RelativePathCommand::V(dy) => format!("v {dy:.12}"),
RelativePathCommand::C(dx1, dy1, dx2, dy2, dx, dy) => format!("c{:.12},{:.12} {:.12},{:.12} {:.12},{:.12}", dx1, dy1, dx2, dy2, dx, dy), RelativePathCommand::C(dx1, dy1, dx2, dy2, dx, dy) => format!("c{dx1:.12},{dy1:.12} {dx2:.12},{dy2:.12} {dx:.12},{dy:.12}"),
RelativePathCommand::S(dx2, dy2, dx, dy) => { RelativePathCommand::S(dx2, dy2, dx, dy) => {
format!("s {:.12},{:.12} {:.12},{:.12}", dx2, dy2, dx, dy) format!("s {dx2:.12},{dy2:.12} {dx:.12},{dy:.12}")
} }
RelativePathCommand::Q(dx1, dy1, dx, dy) => { RelativePathCommand::Q(dx1, dy1, dx, dy) => {
format!("q {:.12},{:.12} {:.12},{:.12}", dx1, dy1, dx, dy) format!("q {dx1:.12},{dy1:.12} {dx:.12},{dy:.12}")
} }
RelativePathCommand::T(dx, dy) => format!("t{:.12},{:.12}", dx, dy), RelativePathCommand::T(dx, dy) => format!("t{dx:.12},{dy:.12}"),
RelativePathCommand::A(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, dx, dy) => { RelativePathCommand::A(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, dx, dy) => {
format!("a {:.12} {:.12} {:.12} {} {} {:.12},{:.12}", rx, ry, x_axis_rotation, large_arc_flag as u8, sweep_flag as u8, dx, dy) format!("a {:.12} {:.12} {:.12} {} {} {:.12},{:.12}", rx, ry, x_axis_rotation, large_arc_flag as u8, sweep_flag as u8, dx, dy)
} }
+3 -1
View File
@@ -551,13 +551,15 @@ fn round_point(point: DVec2) -> I64Vec2 {
(point * ROUNDING_FACTOR).round().as_i64vec2() (point * ROUNDING_FACTOR).round().as_i64vec2()
} }
type Edges = SmallVec<[(PathSegment, u8, MajorEdgeKey, MajorEdgeKey); 2]>;
fn find_vertices(edges: &[MajorGraphEdgeStage1]) -> MajorGraph { fn find_vertices(edges: &[MajorGraphEdgeStage1]) -> MajorGraph {
let mut graph = MajorGraph { let mut graph = MajorGraph {
edges: SlotMap::with_capacity_and_key(edges.len() * 2), edges: SlotMap::with_capacity_and_key(edges.len() * 2),
vertices: SlotMap::with_capacity_and_key(edges.len()), vertices: SlotMap::with_capacity_and_key(edges.len()),
}; };
let mut vertex_pair_id_to_edges: HashMap<_, SmallVec<[(PathSegment, u8, MajorEdgeKey, MajorEdgeKey); 2]>> = new_hash_map(edges.len()); let mut vertex_pair_id_to_edges: HashMap<_, Edges> = new_hash_map(edges.len());
let mut vertex_hashmap: HashMap<I64Vec2, MajorVertexKey> = new_hash_map(edges.len() * 2); let mut vertex_hashmap: HashMap<I64Vec2, MajorVertexKey> = new_hash_map(edges.len() * 2);
for (seg, parent) in edges { for (seg, parent) in edges {
+1 -1
View File
@@ -11,7 +11,7 @@ The graph that is presented to users in the editor is known as the document grap
```rs ```rs
pub struct DocumentNode { pub struct DocumentNode {
pub inputs: Vec<NodeInput>, pub inputs: Vec<NodeInput>,
pub manual_composition: Option<Type>, pub call_argument: Type,
pub implementation: DocumentNodeImplementation, pub implementation: DocumentNodeImplementation,
pub skip_deduplication: bool, pub skip_deduplication: bool,
pub visible: bool, pub visible: bool,
+1 -1
View File
@@ -249,7 +249,7 @@ struct Logger;
impl NodeGraphUpdateSender for Logger { impl NodeGraphUpdateSender for Logger {
fn send(&self, message: NodeGraphUpdateMessage) { fn send(&self, message: NodeGraphUpdateMessage) {
log::warn!("dispatching message with fallback node graph update sender {:?}", message); log::warn!("dispatching message with fallback node graph update sender {message:?}");
} }
} }
+2 -2
View File
@@ -1,7 +1,7 @@
mod color;
mod color_traits; mod color_traits;
mod color_types;
mod discrete_srgb; mod discrete_srgb;
pub use color::*;
pub use color_traits::*; pub use color_traits::*;
pub use color_types::*;
pub use discrete_srgb::*; pub use discrete_srgb::*;
-1
View File
@@ -21,7 +21,6 @@ pub mod raster;
pub mod raster_types; pub mod raster_types;
pub mod registry; pub mod registry;
pub mod render_complexity; pub mod render_complexity;
pub mod structural;
pub mod subpath; pub mod subpath;
pub mod table; pub mod table;
pub mod text; pub mod text;
+1 -1
View File
@@ -287,6 +287,6 @@ mod test {
fn display() { fn display() {
let p = Polynomial::new([1., 2., 0., 3.]); let p = Polynomial::new([1., 2., 0., 3.]);
assert_eq!(format!("{:.2}", p), "3.00x^3 + 2.00x + 1.00"); assert_eq!(format!("{p:.2}"), "3.00x^3 + 2.00x + 1.00");
} }
} }
+23 -7
View File
@@ -8,6 +8,21 @@ use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
/// Caches the output of a given Node and acts as a proxy /// Caches the output of a given Node and acts as a proxy
///
/// ```text
/// ┌───────────────┐ ┌───────────────┐
/// │ │◄───┤ │◄─── EVAL (START)
/// │ CacheNode │ │ F │
/// │ ├───►│ │───► RESULT (END)
/// ┌───────────────┐ ├───────────────┤ └───────────────┘
/// │ │◄───┤ │
/// │ G │ │ Cached Data │
/// │ ├───►│ │
/// └───────────────┘ └───────────────┘
/// ```
///
/// The call from `F` directly reaches the `CacheNode` and the `CacheNode` can decide whether to call `G.eval(input_from_f)`
/// in the event of a cache miss or just return the cached data in the event of a cache hit.
#[derive(Default)] #[derive(Default)]
pub struct MemoNode<T, CachedNode> { pub struct MemoNode<T, CachedNode> {
cache: Arc<Mutex<Option<(u64, T)>>>, cache: Arc<Mutex<Option<(u64, T)>>>,
@@ -50,6 +65,7 @@ impl<T, CachedNode> MemoNode<T, CachedNode> {
} }
} }
#[allow(clippy::module_inception)]
pub mod memo { pub mod memo {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MemoNode"); pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MemoNode");
} }
@@ -155,10 +171,10 @@ pub mod monitor {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode"); pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode");
} }
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct MemoHash<T: Hash> { pub struct MemoHash<T: Hash> {
hash: u64, hash: u64,
value: T, value: Arc<T>,
} }
impl<'de, T: serde::Deserialize<'de> + Hash> serde::Deserialize<'de> for MemoHash<T> { impl<'de, T: serde::Deserialize<'de> + Hash> serde::Deserialize<'de> for MemoHash<T> {
@@ -182,10 +198,10 @@ impl<T: Hash + serde::Serialize> serde::Serialize for MemoHash<T> {
impl<T: Hash> MemoHash<T> { impl<T: Hash> MemoHash<T> {
pub fn new(value: T) -> Self { pub fn new(value: T) -> Self {
let hash = Self::calc_hash(&value); let hash = Self::calc_hash(&value);
Self { hash, value } Self { hash, value: value.into() }
} }
pub fn new_with_hash(value: T, hash: u64) -> Self { pub fn new_with_hash(value: T, hash: u64) -> Self {
Self { hash, value } Self { hash, value: value.into() }
} }
fn calc_hash(data: &T) -> u64 { fn calc_hash(data: &T) -> u64 {
@@ -197,7 +213,7 @@ impl<T: Hash> MemoHash<T> {
pub fn inner_mut(&mut self) -> MemoHashGuard<'_, T> { pub fn inner_mut(&mut self) -> MemoHashGuard<'_, T> {
MemoHashGuard { inner: self } MemoHashGuard { inner: self }
} }
pub fn into_inner(self) -> T { pub fn into_inner(self) -> Arc<T> {
self.value self.value
} }
pub fn hash_code(&self) -> u64 { pub fn hash_code(&self) -> u64 {
@@ -243,8 +259,8 @@ impl<T: Hash> Deref for MemoHashGuard<'_, T> {
} }
} }
impl<T: Hash> std::ops::DerefMut for MemoHashGuard<'_, T> { impl<T: Hash + Clone> std::ops::DerefMut for MemoHashGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner.value Arc::make_mut(&mut self.inner.value)
} }
} }
+8 -41
View File
@@ -1,3 +1,5 @@
use graphene_core_shaders::Ctx;
use crate::Node; use crate::Node;
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -41,28 +43,9 @@ impl<'i, N: for<'a> Node<'a, I> + Clone, I: 'i> Clone for TypeNode<N, I, <N as N
} }
impl<'i, N: for<'a> Node<'a, I> + Copy, I: 'i> Copy for TypeNode<N, I, <N as Node<'i, I>>::Output> {} impl<'i, N: for<'a> Node<'a, I> + Copy, I: 'i> Copy for TypeNode<N, I, <N as Node<'i, I>>::Output> {}
// Into #[node_macro::node(skip_impl)]
pub struct IntoNode<O>(PhantomData<O>); fn into<'i, T: 'i + Send + Into<O>, O: 'i + Send>(_: impl Ctx, value: T, _out_ty: PhantomData<O>) -> O {
impl<O> IntoNode<O> { value.into()
pub const fn new() -> Self {
Self(PhantomData)
}
}
impl<O> Default for IntoNode<O> {
fn default() -> Self {
Self::new()
}
}
impl<'input, I: 'input, O: 'input> Node<'input, I> for IntoNode<O>
where
I: Into<O> + Sync + Send,
{
type Output = dyn_any::DynFuture<'input, O>;
#[inline]
fn eval(&'input self, input: I) -> Self::Output {
Box::pin(async move { input.into() })
}
} }
/// The [`Convert`] trait allows for conversion between Rust primitive numeric types. /// The [`Convert`] trait allows for conversion between Rust primitive numeric types.
@@ -122,25 +105,9 @@ impl_convert!(u128);
impl_convert!(isize); impl_convert!(isize);
impl_convert!(usize); impl_convert!(usize);
// Convert #[node_macro::node(skip_impl)]
pub struct ConvertNode<O>(PhantomData<O>); fn convert<'i, T: 'i + Send + Convert<O>, O: 'i + Send>(_: impl Ctx, value: T, _out_ty: PhantomData<O>) -> O {
impl<_O> ConvertNode<_O> { value.convert()
pub const fn new() -> Self {
Self(core::marker::PhantomData)
}
}
impl<_O> Default for ConvertNode<_O> {
fn default() -> Self {
Self::new()
}
}
impl<'input, I: 'input + Convert<_O> + Sync + Send, _O: 'input> Node<'input, I> for ConvertNode<_O> {
type Output = ::dyn_any::DynFuture<'input, _O>;
#[inline]
fn eval(&'input self, input: I) -> Self::Output {
Box::pin(async move { input.convert() })
}
} }
#[cfg(test)] #[cfg(test)]
+3 -3
View File
@@ -258,7 +258,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self { GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
image: image.iter().next().unwrap().element.clone(), image: image.iter().next().unwrap().element.clone(),
}, },
_ => panic!("Expected Image, found {:?}", element), _ => panic!("Expected Image, found {element:?}"),
} }
} }
} }
@@ -478,9 +478,9 @@ mod test {
}; };
let serialized = serde_json::to_string(&image).unwrap(); let serialized = serde_json::to_string(&image).unwrap();
println!("{}", serialized); println!("{serialized}");
let deserialized: Image<Color> = serde_json::from_str(&serialized).unwrap(); let deserialized: Image<Color> = serde_json::from_str(&serialized).unwrap();
println!("{:?}", deserialized); println!("{deserialized:?}");
assert_eq!(image, deserialized); assert_eq!(image, deserialized);
} }
+3 -2
View File
@@ -153,13 +153,14 @@ where
{ {
type Output = DynFuture<'input, O>; type Output = DynFuture<'input, O>;
#[inline] #[inline]
#[track_caller]
fn eval(&'input self, input: I) -> Self::Output { fn eval(&'input self, input: I) -> Self::Output {
{ {
let node_name = self.node.node_name(); let node_name = self.node.node_name();
let input = Box::new(input); let input = Box::new(input);
let future = self.node.eval(input); let future = self.node.eval(input);
Box::pin(async move { Box::pin(async move {
let out = dyn_any::downcast(future.await).unwrap_or_else(|e| panic!("DowncastBothNode Input {e} in: \n{node_name}")); let out = dyn_any::downcast(future.await).unwrap_or_else(|e| panic!("DowncastBothNode wrong output type: {e} in: \n{node_name}"));
*out *out
}) })
} }
@@ -234,7 +235,7 @@ where
}; };
match dyn_any::downcast(input) { match dyn_any::downcast(input) {
Ok(input) => Box::pin(output(*input)), Ok(input) => Box::pin(output(*input)),
Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, node_name), Err(e) => panic!("DynAnyNode Input, {e} in:\n{node_name}"),
} }
} }
-153
View File
@@ -1,153 +0,0 @@
use crate::Node;
use std::marker::PhantomData;
/// This is how we can generically define composition of two nodes.
/// This is done generically as shown: <https://files.keavon.com/-/SurprisedGaseousAnhinga/capture.png>
/// A concrete example: <https://files.keavon.com/-/ExcitableGoldRay/capture.png>
/// And showing the direction of data flow: <https://files.keavon.com/-/SoreShimmeringElephantseal/capture.png>
/// ```text
/// ┌────────────────┐
/// T │ │ U
/// ───────────►│ Compose Node ├───────────►
/// │ │
/// └────┬───────────┤
/// ┌──────────┐ │ │
/// │ │ T -> V │ │
/// │ First ├─────────────►│ │
/// │ │ │ │
/// └──────────┘ │ │
/// ┌──────────┐ │ │
/// │ │ V -> U │ │
/// │ Second ├─────────────►│ │
/// │ │ └───────────┘
/// └──────────┘
/// ```
#[derive(Clone, Copy)]
pub struct ComposeNode<First, Second, I> {
first: First,
second: Second,
phantom: PhantomData<I>,
}
impl<'i, Input: 'i, First, Second> Node<'i, Input> for ComposeNode<First, Second, Input>
where
First: Node<'i, Input>,
Second: Node<'i, <First as Node<'i, Input>>::Output> + 'i,
{
type Output = <Second as Node<'i, <First as Node<'i, Input>>::Output>>::Output;
fn eval(&'i self, input: Input) -> Self::Output {
let arg = self.first.eval(input);
let second = &self.second;
second.eval(arg)
}
}
impl<First, Second, Input> ComposeNode<First, Second, Input> {
pub const fn new(first: First, second: Second) -> Self {
ComposeNode::<First, Second, Input> { first, second, phantom: PhantomData }
}
}
#[derive(Clone)]
pub struct AsyncComposeNode<First, Second, I> {
first: First,
second: Second,
phantom: PhantomData<I>,
}
impl<'i, Input: 'static, First, Second> Node<'i, Input> for AsyncComposeNode<First, Second, Input>
where
First: Node<'i, Input>,
First::Output: Future,
Second: Node<'i, <<First as Node<'i, Input>>::Output as Future>::Output> + 'i,
{
type Output = std::pin::Pin<Box<dyn Future<Output = <Second as Node<'i, <<First as Node<'i, Input>>::Output as Future>::Output>>::Output> + 'i>>;
fn eval(&'i self, input: Input) -> Self::Output {
Box::pin(async move {
let arg = self.first.eval(input).await;
self.second.eval(arg)
})
}
}
impl<'i, First, Second, Input: 'i> AsyncComposeNode<First, Second, Input>
where
First: Node<'i, Input>,
First::Output: Future,
Second: Node<'i, <<First as Node<'i, Input>>::Output as Future>::Output> + 'i,
{
pub const fn new(first: First, second: Second) -> Self {
AsyncComposeNode::<First, Second, Input> { first, second, phantom: PhantomData }
}
}
pub trait Then<'i, Input: 'i>: Sized {
fn then<Second>(self, second: Second) -> ComposeNode<Self, Second, Input>
where
Self: Node<'i, Input>,
Second: Node<'i, <Self as Node<'i, Input>>::Output>,
{
ComposeNode::new(self, second)
}
}
impl<'i, First: Node<'i, Input>, Input: 'i> Then<'i, Input> for First {}
pub trait AndThen<'i, Input: 'i>: Sized {
fn and_then<Second>(self, second: Second) -> AsyncComposeNode<Self, Second, Input>
where
Self: Node<'i, Input>,
Self::Output: Future,
Second: Node<'i, <<Self as Node<'i, Input>>::Output as Future>::Output> + 'i,
{
AsyncComposeNode::new(self, second)
}
}
impl<'i, First: Node<'i, Input>, Input: 'i> AndThen<'i, Input> for First {}
pub struct ConsNode<I: From<()>, Root>(pub Root, PhantomData<I>);
impl<'i, Root, Input: 'i, I: 'i + From<()>> Node<'i, Input> for ConsNode<I, Root>
where
Root: Node<'i, I>,
{
type Output = (Input, Root::Output);
fn eval(&'i self, input: Input) -> Self::Output {
let arg = self.0.eval(I::from(()));
(input, arg)
}
}
impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode<I, Root> {
pub fn new(root: Root) -> Self {
ConsNode(root, PhantomData)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::generic::FnNode;
use crate::value::ValueNode;
#[test]
fn compose() {
let value = ValueNode::new(4u32);
let compose = value.then(FnNode::new(|x| x));
assert_eq!(compose.eval(()), &4u32);
let type_erased = &compose as &dyn Node<'_, (), Output = &'_ u32>;
assert_eq!(type_erased.eval(()), &4u32);
}
#[test]
fn test_ref_eval() {
let value = ValueNode::new(5);
assert_eq!(value.eval(()), &5);
let id = FnNode::new(|x| x);
let compose = ComposeNode::new(&value, &id);
assert_eq!(compose.eval(()), &5);
}
}
+2 -3
View File
@@ -230,7 +230,6 @@ pub enum Type {
/// A wrapper around the Rust type id for any concrete Rust type. Allows us to do equality comparisons, like checking if a String == a String. /// A wrapper around the Rust type id for any concrete Rust type. Allows us to do equality comparisons, like checking if a String == a String.
Concrete(TypeDescriptor), Concrete(TypeDescriptor),
/// Runtime type information for a function. Given some input, gives some output. /// Runtime type information for a function. Given some input, gives some output.
/// See the example and explanation in the `ComposeNode` implementation within the node registry for more info.
Fn(Box<Type>, Box<Type>), Fn(Box<Type>, Box<Type>),
/// Represents a future which promises to return the inner type. /// Represents a future which promises to return the inner type.
Future(Box<Type>), Future(Box<Type>),
@@ -367,7 +366,7 @@ impl std::fmt::Debug for Type {
Self::Future(ty) => format!("{ty:?}"), Self::Future(ty) => format!("{ty:?}"),
}; };
let result = result.replace("Option<Arc<OwnedContextImpl>>", "Context"); let result = result.replace("Option<Arc<OwnedContextImpl>>", "Context");
write!(f, "{}", result) write!(f, "{result}")
} }
} }
@@ -380,6 +379,6 @@ impl std::fmt::Display for Type {
Type::Future(ty) => ty.to_string(), Type::Future(ty) => ty.to_string(),
}; };
let result = result.replace("Option<Arc<OwnedContextImpl>>", "Context"); let result = result.replace("Option<Arc<OwnedContextImpl>>", "Context");
write!(f, "{}", result) write!(f, "{result}")
} }
} }
+1 -2
View File
@@ -60,8 +60,7 @@ impl<'i, T: 'i> Node<'i, ()> for RefCellMutNode<T> {
type Output = RefMut<'i, T>; type Output = RefMut<'i, T>;
#[inline(always)] #[inline(always)]
fn eval(&'i self, _input: ()) -> Self::Output { fn eval(&'i self, _input: ()) -> Self::Output {
let a = self.0.borrow_mut(); self.0.borrow_mut()
a
} }
} }
@@ -225,7 +225,7 @@ pub fn pathseg_find_tvalues_for_x(segment: PathSeg, x: f64) -> impl Iterator<Ite
let b = 2.0 * (p1.x - p0.x); let b = 2.0 * (p1.x - p0.x);
let c = p0.x - x; let c = p0.x - x;
let r = solve_quadratic(c, b, a); let r = solve_quadratic(c, b, a);
[r.get(0).map(|t| *t), r.get(1).map(|t| *t), None] [r.first().copied(), r.get(1).copied(), None]
} }
PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => { PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => {
let a = p3.x - 3.0 * p2.x + 3.0 * p1.x - p0.x; let a = p3.x - 3.0 * p2.x + 3.0 * p1.x - p0.x;
@@ -233,7 +233,7 @@ pub fn pathseg_find_tvalues_for_x(segment: PathSeg, x: f64) -> impl Iterator<Ite
let c = 3.0 * (p1.x - p0.x); let c = 3.0 * (p1.x - p0.x);
let d = p0.x - x; let d = p0.x - x;
let r = solve_cubic(d, c, b, a); let r = solve_cubic(d, c, b, a);
[r.get(0).map(|t| *t), r.get(1).map(|t| *t), r.get(2).map(|t| *t)] [r.first().copied(), r.get(1).copied(), r.get(2).copied()]
} }
} }
.into_iter() .into_iter()
@@ -307,7 +307,7 @@ mod tests {
assert_eq!(bezier.handles, subpath::BezierHandles::Linear); assert_eq!(bezier.handles, subpath::BezierHandles::Linear);
let vector = bezier.start - bezier.end; let vector = bezier.start - bezier.end;
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.; let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;
assert!([90., 150., 40.].into_iter().any(|target| (target - angle).abs() < 1e-10), "unexpected angle of {}", angle) assert!([90., 150., 40.].into_iter().any(|target| (target - angle).abs() < 1e-10), "unexpected angle of {angle}")
} }
} }
} }
+2 -2
View File
@@ -343,8 +343,8 @@ fn to_path_segments(path: &mut Vec<path_bool::PathSegment>, subpath: &Subpath<Po
let PathSegPoints { p0, p1, p2, p3 } = pathseg_points(bezier); let PathSegPoints { p0, p1, p2, p3 } = pathseg_points(bezier);
let p0 = transform_point(p0); let p0 = transform_point(p0);
let p1 = p1.map(|p1| transform_point(p1)); let p1 = p1.map(transform_point);
let p2 = p2.map(|p2| transform_point(p2)); let p2 = p2.map(transform_point);
let p3 = transform_point(p3); let p3 = transform_point(p3);
if global_start.is_none() { if global_start.is_none() {
+41 -227
View File
@@ -1,13 +1,13 @@
pub mod value; pub mod value;
use crate::document::value::TaggedValue; use crate::document::value::TaggedValue;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode};
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::IVec2; use glam::IVec2;
use graphene_core::memo::MemoHashGuard; use graphene_core::memo::MemoHashGuard;
pub use graphene_core::uuid::NodeId; pub use graphene_core::uuid::NodeId;
pub use graphene_core::uuid::generate_uuid; pub use graphene_core::uuid::generate_uuid;
use graphene_core::{Cow, MemoHash, ProtoNodeIdentifier, Type}; use graphene_core::{Context, Cow, MemoHash, ProtoNodeIdentifier, Type};
use log::Metadata; use log::Metadata;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::collections::HashMap; use std::collections::HashMap;
@@ -44,101 +44,9 @@ pub struct DocumentNode {
/// by using network.update_click_target(node_id). /// by using network.update_click_target(node_id).
#[cfg_attr(target_family = "wasm", serde(alias = "outputs"))] #[cfg_attr(target_family = "wasm", serde(alias = "outputs"))]
pub inputs: Vec<NodeInput>, pub inputs: Vec<NodeInput>,
/// Manual composition is the methodology by which most nodes are implemented, involving a call argument and upstream inputs. /// Type of the argument which this node can be evaluated with.
/// By contrast, automatic composition is an alternative way to handle the composition of nodes as they execute in the graph. #[serde(alias = "manual_composition", default)]
/// Normally, the program (the compiled graph) builds up its call stack, with each node calling its upstream predecessor to acquire its input data. pub call_argument: Type,
/// When the document graph becomes the proto graph, that conceptual model changes into a model that's unique to the proto graph.
/// Automatic composition allows a document node to be translated into its place in the proto graph differently, such that
/// the node doesn't participate in that process of being called with a call argument and calling its upstream predecessor.
/// Instead, it is called directly with its input data from the upstream node, skipping the call stack building process.
/// The abstraction is provided by the compiler for nodes which opt for automatic composition. It works by inserting a `ComposeNode`
/// into the proto graph, which does the job of calling the upstream node and feeding its output into the downstream node's first input.
/// That first input is typically used by manual composition nodes as the call argument, but for automatic composition nodes,
/// that first input becomes the input data from the upstream node passed in by the `ComposeNode`.
///
/// Through automatic composition, the upstream node providing the first input for a proto node is evaluated before the proto node itself is run.
/// (That first input is usually the call argument when manual composition is used.)
/// - Abstract example: upstream node `G` is evaluated and its data feeds into the first input of downstream node `F`,
/// just like function composition where function `G` is evaluated and its result is fed into function `F`.
/// - Concrete example: a node that takes an image as its first input will get that image data from an upstream node that produces image output data and is evaluated first before being fed downstream.
///
/// This is achieved by automatically inserting `ComposeNode`s, which run the first node with the overall input and then feed the resulting output into the second node.
/// The `ComposeNode` is basically a function composition operator: the parentheses in `F(G(x))` or circle math operator in `(F ∘ G)(x)`.
/// For flexibility, instead of being a language construct, Graphene splits out composition itself as its own low-level node so that behavior can be overridden.
/// The `ComposeNode`s are then inserted during the graph rewriting step for nodes that don't opt out with `manual_composition`.
/// Instead of node `G` feeding into node `F` feeding as the result back to the caller,
/// the graph is rewritten so nodes `G` and `F` both feed as lambdas into the inputs of a `ComposeNode` which calls `F(G(input))` and returns the result to the caller.
///
/// A node's manual composition input represents an input that is not resolved through graph rewriting with a `ComposeNode`,
/// and is instead just passed in when evaluating this node within the borrow tree.
/// This is similar to having the first input be a `NodeInput::Network` after the graph flattening.
///
/// ## Example Use Case: CacheNode
///
/// The `CacheNode` is a pass-through node on cache miss, but on cache hit it needs to avoid evaluating the upstream node and instead just return the cached value.
///
/// First, let's consider what that would look like using the default composition flow if the `CacheNode` instead just always acted as a pass-through (akin to a cache that always misses):
///
/// ```text
/// ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
/// │ │◄───┤ │◄───┤ │◄─── EVAL (START)
/// │ G │ │PassThroughNode│ │ F │
/// │ ├───►│ ├───►│ │───► RESULT (END)
/// └───────────────┘ └───────────────┘ └───────────────┘
/// ```
///
/// This acts like the function call `F(PassThroughNode(G(input)))` when evaluating `F` with some `input`: `F.eval(input)`.
/// - The diagram's upper track of arrows represents the flow of building up the call stack:
/// since `F` is the output it is encountered first but deferred to its upstream caller `PassThroughNode` and that is once again deferred to its upstream caller `G`.
/// - The diagram's lower track of arrows represents the flow of evaluating the call stack:
/// `G` is evaluated first, then `PassThroughNode` is evaluated with the result of `G`, and finally `F` is evaluated with the result of `PassThroughNode`.
///
/// With the default composition flow (no manual composition), `ComposeNode`s would be automatically inserted during the graph rewriting step like this:
///
/// ```text
/// ┌───────────────┐
/// │ │◄─── EVAL (START)
/// │ ComposeNode │
/// ┌───────────────┐ │ ├───► RESULT (END)
/// │ │◄─┐ ├───────────────┤
/// │ G │ └─┤ │
/// │ ├─┐ │ First │
/// └───────────────┘ └─►│ │
/// ┌───────────────┐ ├───────────────┤
/// │ │◄───┤ │
/// │ ComposeNode │ │ Second │
/// ┌───────────────┐ │ ├───►│ │
/// │ │◄─┐ ├───────────────┤ └───────────────┘
/// │PassThroughNode│ └─┤ │
/// │ ├─┐ │ First │
/// └───────────────┘ └─►│ │
/// ┌───────────────┐ ├───────────────┤
/// | │◄───┤ │
/// │ F │ │ Second │
/// │ ├───►│ │
/// └───────────────┘ └───────────────┘
/// ```
///
/// Now let's swap back from the `PassThroughNode` to the `CacheNode` to make caching actually work.
/// It needs to override the default composition flow so that `G` is not automatically evaluated when the cache is hit.
/// We need to give the `CacheNode` more manual control over the order of execution.
/// So the `CacheNode` opts into manual composition and, instead of deferring to its upstream caller, it consumes the input directly:
///
/// ```text
/// ┌───────────────┐ ┌───────────────┐
/// │ │◄───┤ │◄─── EVAL (START)
/// │ CacheNode │ │ F │
/// │ ├───►│ │───► RESULT (END)
/// ┌───────────────┐ ├───────────────┤ └───────────────┘
/// │ │◄───┤ │
/// │ G │ │ Cached Data │
/// │ ├───►│ │
/// └───────────────┘ └───────────────┘
/// ```
///
/// Now, the call from `F` directly reaches the `CacheNode` and the `CacheNode` can decide whether to call `G.eval(input_from_f)`
/// in the event of a cache miss or just return the cached data in the event of a cache hit.
pub manual_composition: Option<Type>,
// A nested document network or a proto-node identifier. // A nested document network or a proto-node identifier.
pub implementation: DocumentNodeImplementation, pub implementation: DocumentNodeImplementation,
/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step. /// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step.
@@ -173,15 +81,13 @@ pub struct OriginalLocation {
pub dependants: Vec<Vec<NodeId>>, pub dependants: Vec<Vec<NodeId>>,
/// A list of flags indicating whether the input is exposed in the UI /// A list of flags indicating whether the input is exposed in the UI
pub inputs_exposed: Vec<bool>, pub inputs_exposed: Vec<bool>,
/// Skipping inputs is useful for the manual composition thing - whereby a hidden `Footprint` input is added as the first input.
pub skip_inputs: usize,
} }
impl Default for DocumentNode { impl Default for DocumentNode {
fn default() -> Self { fn default() -> Self {
Self { Self {
inputs: Default::default(), inputs: Default::default(),
manual_composition: Default::default(), call_argument: concrete!(Context),
implementation: Default::default(), implementation: Default::default(),
visible: true, visible: true,
skip_deduplication: Default::default(), skip_deduplication: Default::default(),
@@ -195,14 +101,13 @@ impl Hash for OriginalLocation {
self.path.hash(state); self.path.hash(state);
self.inputs_source.iter().for_each(|val| val.hash(state)); self.inputs_source.iter().for_each(|val| val.hash(state));
self.inputs_exposed.hash(state); self.inputs_exposed.hash(state);
self.skip_inputs.hash(state);
} }
} }
impl OriginalLocation { impl OriginalLocation {
pub fn inputs(&self, index: usize) -> impl Iterator<Item = Source> + '_ { pub fn inputs(&self, index: usize) -> impl Iterator<Item = Source> + '_ {
[(index >= self.skip_inputs).then(|| Source { [(index >= 1).then(|| Source {
node: self.path.clone().unwrap_or_default(), node: self.path.clone().unwrap_or_default(),
index: self.inputs_exposed.iter().take(index - self.skip_inputs).filter(|&&exposed| exposed).count(), index: self.inputs_exposed.iter().take(index - 1).filter(|&&exposed| exposed).count(),
})] })]
.into_iter() .into_iter()
.flatten() .flatten()
@@ -211,7 +116,7 @@ impl OriginalLocation {
} }
impl DocumentNode { impl DocumentNode {
/// Locate the input that is a [`NodeInput::Network`] at index `offset` and replace it with a [`NodeInput::Node`]. /// Locate the input that is a [`NodeInput::Network`] at index `offset` and replace it with a [`NodeInput::Node`].
pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize, lambda: bool, source: impl Iterator<Item = Source>, skip: usize) { pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize, source: impl Iterator<Item = Source>, skip: usize) {
let (index, _) = self let (index, _) = self
.inputs .inputs
.iter() .iter()
@@ -219,60 +124,38 @@ impl DocumentNode {
.nth(offset) .nth(offset)
.unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}")); .unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}"));
self.inputs[index] = NodeInput::Node { node_id, output_index, lambda }; self.inputs[index] = NodeInput::Node { node_id, output_index };
let input_source = &mut self.original_location.inputs_source; let input_source = &mut self.original_location.inputs_source;
for source in source { for source in source {
input_source.insert(source, (index + self.original_location.skip_inputs).saturating_sub(skip)); input_source.insert(source, (index + 1).saturating_sub(skip));
} }
} }
fn resolve_proto_node(mut self) -> ProtoNode { fn resolve_proto_node(self) -> ProtoNode {
assert!(!self.inputs.is_empty() || self.manual_composition.is_some(), "Resolving document node {self:#?} with no inputs");
let DocumentNodeImplementation::ProtoNode(identifier) = self.implementation else { let DocumentNodeImplementation::ProtoNode(identifier) = self.implementation else {
unreachable!("tried to resolve not flattened node on resolved node {self:?}"); unreachable!("tried to resolve not flattened node on resolved node {self:?}");
}; };
let (input, mut args) = if let Some(ty) = self.manual_composition { let (input, mut args) = (self.call_argument, ConstructionArgs::Nodes(vec![]));
(ProtoNodeInput::ManualComposition(ty), ConstructionArgs::Nodes(vec![]))
} else {
let first = self.inputs.remove(0);
match first {
NodeInput::Value { tagged_value, .. } => {
assert_eq!(self.inputs.len(), 0, "A value node cannot have any inputs. Current inputs: {:?}", self.inputs);
(ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context<'static>)), ConstructionArgs::Value(tagged_value))
}
NodeInput::Node { node_id, output_index, lambda } => {
assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node");
let node = if lambda { ProtoNodeInput::NodeLambda(node_id) } else { ProtoNodeInput::Node(node_id) };
(node, ConstructionArgs::Nodes(vec![]))
}
NodeInput::Network { import_type, .. } => (ProtoNodeInput::ManualComposition(import_type), ConstructionArgs::Nodes(vec![])),
NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)),
NodeInput::Scope(_) => unreachable!("Scope input was not resolved"),
NodeInput::Reflection(_) => unreachable!("Reflection input was not resolved"),
}
};
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network { .. })), "received non-resolved input"); assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network { .. })), "received non-resolved input");
assert!(
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
"received value as input. inputs: {:#?}, construction_args: {:#?}",
self.inputs,
args
);
// If we have one input of the type inline, set it as the construction args // If we have one input of the type inline, set it as the construction args
if let &[NodeInput::Inline(ref inline)] = self.inputs.as_slice() { if let &[NodeInput::Inline(ref inline)] = self.inputs.as_slice() {
args = ConstructionArgs::Inline(inline.clone()); args = ConstructionArgs::Inline(inline.clone());
} }
// If we have one input of the type inline, set it as the construction args
if let &[NodeInput::Value { ref tagged_value, .. }] = self.inputs.as_slice() {
args = ConstructionArgs::Value(tagged_value.clone());
}
if let ConstructionArgs::Nodes(nodes) = &mut args { if let ConstructionArgs::Nodes(nodes) = &mut args {
nodes.extend(self.inputs.iter().map(|input| match input { nodes.extend(self.inputs.iter().map(|input| match input {
NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda), NodeInput::Node { node_id, .. } => *node_id,
_ => unreachable!(), _ => unreachable!(),
})); }));
} }
ProtoNode { ProtoNode {
identifier, identifier,
input, call_argument: input,
construction_args: args, construction_args: args,
original_location: self.original_location, original_location: self.original_location,
skip_deduplication: self.skip_deduplication, skip_deduplication: self.skip_deduplication,
@@ -284,7 +167,7 @@ impl DocumentNode {
#[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
pub enum NodeInput { pub enum NodeInput {
/// A reference to another node in the same network from which this node can receive its input. /// A reference to another node in the same network from which this node can receive its input.
Node { node_id: NodeId, output_index: usize, lambda: bool }, Node { node_id: NodeId, output_index: usize },
/// A hardcoded value that can't change after the graph is compiled. Gets converted into a value node during graph compilation. /// A hardcoded value that can't change after the graph is compiled. Gets converted into a value node during graph compilation.
Value { tagged_value: MemoHash<TaggedValue>, exposed: bool }, Value { tagged_value: MemoHash<TaggedValue>, exposed: bool },
@@ -323,11 +206,7 @@ pub enum DocumentNodeMetadata {
impl NodeInput { impl NodeInput {
pub const fn node(node_id: NodeId, output_index: usize) -> Self { pub const fn node(node_id: NodeId, output_index: usize) -> Self {
Self::Node { node_id, output_index, lambda: false } Self::Node { node_id, output_index }
}
pub const fn lambda(node_id: NodeId, output_index: usize) -> Self {
Self::Node { node_id, output_index, lambda: true }
} }
pub fn value(tagged_value: TaggedValue, exposed: bool) -> Self { pub fn value(tagged_value: TaggedValue, exposed: bool) -> Self {
@@ -344,12 +223,8 @@ impl NodeInput {
} }
fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) { fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {
if let &mut NodeInput::Node { node_id, output_index, lambda } = self { if let &mut NodeInput::Node { node_id, output_index } = self {
*self = NodeInput::Node { *self = NodeInput::Node { node_id: f(node_id), output_index }
node_id: f(node_id),
output_index,
lambda,
}
} }
} }
@@ -772,7 +647,6 @@ impl NodeNetwork {
node.original_location = OriginalLocation { node.original_location = OriginalLocation {
path: Some(new_path), path: Some(new_path),
inputs_exposed: node.inputs.iter().map(|input| input.is_exposed()).collect(), inputs_exposed: node.inputs.iter().map(|input| input.is_exposed()).collect(),
skip_inputs: if node.manual_composition.is_some() { 1 } else { 0 },
dependants: (0..node.implementation.output_count()).map(|_| Vec::new()).collect(), dependants: (0..node.implementation.output_count()).map(|_| Vec::new()).collect(),
..Default::default() ..Default::default()
}; };
@@ -899,7 +773,7 @@ impl NodeNetwork {
// Connect layer node to the group below // Connect layer node to the group below
node.inputs.drain(1..); node.inputs.drain(1..);
node.manual_composition = None; node.call_argument = concrete!(());
self.nodes.insert(id, node); self.nodes.insert(id, node);
return; return;
} }
@@ -949,12 +823,11 @@ impl NodeNetwork {
for (nested_node_id, mut nested_node) in inner_network.nodes.into_iter() { for (nested_node_id, mut nested_node) in inner_network.nodes.into_iter() {
for (nested_input_index, nested_input) in nested_node.clone().inputs.iter().enumerate() { for (nested_input_index, nested_input) in nested_node.clone().inputs.iter().enumerate() {
if let NodeInput::Network { import_index, .. } = nested_input { if let NodeInput::Network { import_index, .. } = nested_input {
let parent_input = node.inputs.get(*import_index).unwrap_or_else(|| panic!("Import index {} should always exist", import_index)); let parent_input = node.inputs.get(*import_index).unwrap_or_else(|| panic!("Import index {import_index} should always exist"));
match *parent_input { match *parent_input {
// If the input to self is a node, connect the corresponding output of the inner network to it // If the input to self is a node, connect the corresponding output of the inner network to it
NodeInput::Node { node_id, output_index, lambda } => { NodeInput::Node { node_id, output_index } => {
let skip = node.original_location.skip_inputs; nested_node.populate_first_network_input(node_id, output_index, nested_input_index, node.original_location.inputs(*import_index), 1);
nested_node.populate_first_network_input(node_id, output_index, nested_input_index, lambda, node.original_location.inputs(*import_index), skip);
let input_node = self.nodes.get_mut(&node_id).unwrap_or_else(|| panic!("unable find input node {node_id:?}")); let input_node = self.nodes.get_mut(&node_id).unwrap_or_else(|| panic!("unable find input node {node_id:?}"));
input_node.original_location.dependants[output_index].push(nested_node_id); input_node.original_location.dependants[output_index].push(nested_node_id);
} }
@@ -1052,20 +925,10 @@ impl NodeNetwork {
*export = NodeInput::Node { *export = NodeInput::Node {
node_id: merged_node_id, node_id: merged_node_id,
output_index: 0, output_index: 0,
lambda: false,
}; };
} }
} }
// /// Locate the export that is a [`NodeInput::Network`] at index `offset` and replace it with a [`NodeInput::Node`].
// fn populate_first_network_export(&mut self, node: &mut DocumentNode, node_id: NodeId, output_index: usize, lambda: bool, export_index: usize, source: impl Iterator<Item = Source>, skip: usize) {
// self.exports[export_index] = NodeInput::Node { node_id, output_index, lambda };
// let input_source = &mut node.original_location.inputs_source;
// for source in source {
// input_source.insert(source, output_index + node.original_location.skip_inputs - skip);
// }
// }
fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> { fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> {
let node = self.nodes.get(&id).ok_or_else(|| format!("Node with id {id} does not exist"))?.clone(); let node = self.nodes.get(&id).ok_or_else(|| format!("Node with id {id} does not exist"))?.clone();
if let DocumentNodeImplementation::ProtoNode(ident) = &node.implementation { if let DocumentNodeImplementation::ProtoNode(ident) = &node.implementation {
@@ -1095,7 +958,7 @@ impl NodeNetwork {
let input_source = &mut output.original_location.inputs_source; let input_source = &mut output.original_location.inputs_source;
for source in node.original_location.inputs(index) { for source in node.original_location.inputs(index) {
input_source.insert(source, index + output.original_location.skip_inputs - node.original_location.skip_inputs); input_source.insert(source, index);
} }
} }
} }
@@ -1240,7 +1103,7 @@ impl<'a> Iterator for RecursiveNodeIter<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode};
use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicU64;
fn gen_node_id() -> NodeId { fn gen_node_id() -> NodeId {
@@ -1319,7 +1182,7 @@ mod test {
nodes: [ nodes: [
id_node.clone(), id_node.clone(),
DocumentNode { DocumentNode {
inputs: vec![NodeInput::lambda(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::Extract, implementation: DocumentNodeImplementation::Extract,
..Default::default() ..Default::default()
}, },
@@ -1365,7 +1228,8 @@ mod test {
#[test] #[test]
fn resolve_proto_node_add() { fn resolve_proto_node_add() {
let document_node = DocumentNode { let document_node = DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
call_argument: concrete!(u32),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()),
..Default::default() ..Default::default()
}; };
@@ -1373,8 +1237,8 @@ mod test {
let proto_node = document_node.resolve_proto_node(); let proto_node = document_node.resolve_proto_node();
let reference = ProtoNode { let reference = ProtoNode {
identifier: "graphene_core::structural::ConsNode".into(), identifier: "graphene_core::structural::ConsNode".into(),
input: ProtoNodeInput::ManualComposition(concrete!(u32)), call_argument: concrete!(u32),
construction_args: ConstructionArgs::Nodes(vec![(NodeId(0), false)]), construction_args: ConstructionArgs::Nodes(vec![NodeId(0)]),
..Default::default() ..Default::default()
}; };
assert_eq!(proto_node, reference); assert_eq!(proto_node, reference);
@@ -1390,13 +1254,12 @@ mod test {
NodeId(10), NodeId(10),
ProtoNode { ProtoNode {
identifier: "graphene_core::structural::ConsNode".into(), identifier: "graphene_core::structural::ConsNode".into(),
input: ProtoNodeInput::ManualComposition(concrete!(u32)), call_argument: concrete!(u32),
construction_args: ConstructionArgs::Nodes(vec![(NodeId(14), false)]), construction_args: ConstructionArgs::Nodes(vec![NodeId(14)]),
original_location: OriginalLocation { original_location: OriginalLocation {
path: Some(vec![NodeId(1), NodeId(0)]), path: Some(vec![NodeId(1), NodeId(0)]),
inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(), inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(),
inputs_exposed: vec![true, true], inputs_exposed: vec![true, true],
skip_inputs: 0,
..Default::default() ..Default::default()
}, },
@@ -1407,13 +1270,12 @@ mod test {
NodeId(11), NodeId(11),
ProtoNode { ProtoNode {
identifier: "graphene_core::ops::AddPairNode".into(), identifier: "graphene_core::ops::AddPairNode".into(),
input: ProtoNodeInput::Node(NodeId(10)), call_argument: concrete!(Context),
construction_args: ConstructionArgs::Nodes(vec![]), construction_args: ConstructionArgs::Nodes(vec![NodeId(10)]),
original_location: OriginalLocation { original_location: OriginalLocation {
path: Some(vec![NodeId(1), NodeId(1)]), path: Some(vec![NodeId(1), NodeId(1)]),
inputs_source: HashMap::new(), inputs_source: HashMap::new(),
inputs_exposed: vec![true], inputs_exposed: vec![true],
skip_inputs: 0,
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()
@@ -1423,13 +1285,12 @@ mod test {
NodeId(14), NodeId(14),
ProtoNode { ProtoNode {
identifier: "graphene_core::value::ClonedNode".into(), identifier: "graphene_core::value::ClonedNode".into(),
input: ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context)), call_argument: concrete!(graphene_core::Context),
construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()), construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()),
original_location: OriginalLocation { original_location: OriginalLocation {
path: Some(vec![NodeId(1), NodeId(4)]), path: Some(vec![NodeId(1), NodeId(4)]),
inputs_source: HashMap::new(), inputs_source: HashMap::new(),
inputs_exposed: vec![true, false], inputs_exposed: vec![true, false],
skip_inputs: 0,
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()
@@ -1455,13 +1316,13 @@ mod test {
( (
NodeId(10), NodeId(10),
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::node(NodeId(14), 0)], inputs: vec![NodeInput::node(NodeId(14), 0)],
call_argument: concrete!(u32),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()), implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()),
original_location: OriginalLocation { original_location: OriginalLocation {
path: Some(vec![NodeId(1), NodeId(0)]), path: Some(vec![NodeId(1), NodeId(0)]),
inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(), inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(),
inputs_exposed: vec![true, true], inputs_exposed: vec![true, true],
skip_inputs: 0,
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()
@@ -1476,7 +1337,6 @@ mod test {
path: Some(vec![NodeId(1), NodeId(4)]), path: Some(vec![NodeId(1), NodeId(4)]),
inputs_source: HashMap::new(), inputs_source: HashMap::new(),
inputs_exposed: vec![true, false], inputs_exposed: vec![true, false],
skip_inputs: 0,
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()
@@ -1491,7 +1351,6 @@ mod test {
path: Some(vec![NodeId(1), NodeId(1)]), path: Some(vec![NodeId(1), NodeId(1)]),
inputs_source: HashMap::new(), inputs_source: HashMap::new(),
inputs_exposed: vec![true], inputs_exposed: vec![true],
skip_inputs: 0,
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()
@@ -1576,49 +1435,4 @@ mod test {
} }
// TODO: Write more tests // TODO: Write more tests
// #[test]
// fn out_of_order_duplicate() {
// let result = output_duplicate(vec![NodeInput::node(NodeId(10), 1), NodeInput::node(NodeId(10), 0)], NodeInput::node(NodeId(10), 0);
// assert_eq!(
// result.outputs[0],
// NodeInput::node(NodeId(101), 0),
// "The first network output should be from a duplicated nested network"
// );
// assert_eq!(
// result.outputs[1],
// NodeInput::node(NodeId(10), 0),
// "The second network output should be from the original nested network"
// );
// assert!(
// result.nodes.contains_key(&NodeId(10)) && result.nodes.contains_key(&NodeId(101)) && result.nodes.len() == 2,
// "Network should contain two duplicated nodes"
// );
// for (node_id, input_value, inner_id) in [(10, 1., 1), (101, 2., 2)] {
// let nested_network_node = result.nodes.get(&NodeId(node_id)).unwrap();
// assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change");
// assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(input_value), false)], "Input should be stable");
// let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network");
// assert_eq!(inner_network.inputs, vec![inner_id], "The input should be sent to the second node");
// assert_eq!(inner_network.outputs, vec![NodeInput::node(NodeId(inner_id), 0)], "The output should be node id");
// assert_eq!(inner_network.nodes.get(&NodeId(inner_id)).unwrap().name, format!("Identity {inner_id}"), "The node should be identity");
// }
// }
// #[test]
// fn using_other_node_duplicate() {
// let result = output_duplicate(vec![NodeInput::node(NodeId(11), 0)], NodeInput::node(NodeId(10), 1);
// assert_eq!(result.outputs, vec![NodeInput::node(NodeId(11), 0)], "The network output should be the result node");
// assert!(
// result.nodes.contains_key(&NodeId(11)) && result.nodes.contains_key(&NodeId(101)) && result.nodes.len() == 2,
// "Network should contain a duplicated node and a result node"
// );
// let result_node = result.nodes.get(&NodeId(11)).unwrap();
// assert_eq!(result_node.inputs, vec![NodeInput::node(NodeId(101), 0)], "Result node should refer to duplicate node as input");
// let nested_network_node = result.nodes.get(&NodeId(101)).unwrap();
// assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change");
// assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(2.), false)], "Input should be 2");
// let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network");
// assert_eq!(inner_network.inputs, vec![2], "The input should be sent to the second node");
// assert_eq!(inner_network.outputs, vec![NodeInput::node(NodeId(2), 0)], "The output should be node id 2");
// assert_eq!(inner_network.nodes.get(&NodeId(2)).unwrap().name, "Identity 2", "The node should be identity 2");
// }
} }
+6 -5
View File
@@ -301,13 +301,13 @@ impl TaggedValue {
"MAGENTA" => Color::MAGENTA, "MAGENTA" => Color::MAGENTA,
"TRANSPARENT" => Color::TRANSPARENT, "TRANSPARENT" => Color::TRANSPARENT,
_ => { _ => {
log::error!("Invalid default value color constant: {}", input); log::error!("Invalid default value color constant: {input}");
return None; return None;
} }
}); });
} }
log::error!("Invalid default value color: {}", input); log::error!("Invalid default value color: {input}");
None None
} }
@@ -327,13 +327,13 @@ impl TaggedValue {
"BottomCenter" => ReferencePoint::BottomCenter, "BottomCenter" => ReferencePoint::BottomCenter,
"BottomRight" => ReferencePoint::BottomRight, "BottomRight" => ReferencePoint::BottomRight,
_ => { _ => {
log::error!("Invalid ReferencePoint default type variant: {}", input); log::error!("Invalid ReferencePoint default type variant: {input}");
return None; return None;
} }
}); });
} }
log::error!("Invalid ReferencePoint default type: {}", input); log::error!("Invalid ReferencePoint default type: {input}");
None None
} }
@@ -392,7 +392,8 @@ impl<'input> Node<'input, DAny<'input>> for UpcastNode {
type Output = FutureAny<'input>; type Output = FutureAny<'input>;
fn eval(&'input self, _: DAny<'input>) -> Self::Output { fn eval(&'input self, _: DAny<'input>) -> Self::Output {
Box::pin(async move { self.value.clone().into_inner().to_dynany() }) let memo_clone = MemoHash::clone(&self.value);
Box::pin(async move { memo_clone.into_inner().as_ref().clone().to_dynany() })
} }
} }
impl UpcastNode { impl UpcastNode {
+48 -196
View File
@@ -37,12 +37,7 @@ impl core::fmt::Display for ProtoNetwork {
f.write_str(&"\t".repeat(indent + 1))?; f.write_str(&"\t".repeat(indent + 1))?;
f.write_str("Input: ")?; f.write_str("Input: ")?;
match &node.input { f.write_fmt(format_args!("Call Argument (type = {:?})", node.call_argument))?;
ProtoNodeInput::None => f.write_str("None")?,
ProtoNodeInput::ManualComposition(ty) => f.write_fmt(format_args!("Manual Composition (type = {ty:?})"))?,
ProtoNodeInput::Node(_) => f.write_str("Node")?,
ProtoNodeInput::NodeLambda(_) => f.write_str("Lambda Node")?,
}
f.write_str("\n")?; f.write_str("\n")?;
match &node.construction_args { match &node.construction_args {
@@ -52,7 +47,7 @@ impl core::fmt::Display for ProtoNetwork {
} }
ConstructionArgs::Nodes(nodes) => { ConstructionArgs::Nodes(nodes) => {
for id in nodes { for id in nodes {
write_node(f, network, id.0, indent + 1)?; write_node(f, network, *id, indent + 1)?;
} }
} }
ConstructionArgs::Inline(inline) => { ConstructionArgs::Inline(inline) => {
@@ -78,7 +73,7 @@ pub enum ConstructionArgs {
/// A list of nodes used as inputs to the constructor function in `node_registry.rs`. /// A list of nodes used as inputs to the constructor function in `node_registry.rs`.
/// The bool indicates whether to treat the node as lambda node. /// The bool indicates whether to treat the node as lambda node.
// TODO: use a struct for clearer naming. // TODO: use a struct for clearer naming.
Nodes(Vec<(NodeId, bool)>), Nodes(Vec<NodeId>),
/// Used for GPU computation to work around the limitations of rust-gpu. /// Used for GPU computation to work around the limitations of rust-gpu.
Inline(InlineRust), Inline(InlineRust),
} }
@@ -121,7 +116,7 @@ impl Hash for ConstructionArgs {
impl ConstructionArgs { impl ConstructionArgs {
pub fn new_function_args(&self) -> Vec<String> { pub fn new_function_args(&self) -> Vec<String> {
match self { match self {
ConstructionArgs::Nodes(nodes) => nodes.iter().map(|(n, _)| format!("n{:0x}", n.0)).collect(), ConstructionArgs::Nodes(nodes) => nodes.iter().map(|n| format!("n{:0x}", n.0)).collect(),
ConstructionArgs::Value(value) => vec![value.to_primitive_string()], ConstructionArgs::Value(value) => vec![value.to_primitive_string()],
ConstructionArgs::Inline(inline) => vec![inline.expr.clone()], ConstructionArgs::Inline(inline) => vec![inline.expr.clone()],
} }
@@ -133,7 +128,7 @@ impl ConstructionArgs {
/// At different stages in the compilation process, this struct will be transformed into a reduced (more restricted) form acting as a subset of its original form, but that restricted form is still valid in the earlier stage in the compilation process before it was transformed. /// At different stages in the compilation process, this struct will be transformed into a reduced (more restricted) form acting as a subset of its original form, but that restricted form is still valid in the earlier stage in the compilation process before it was transformed.
pub struct ProtoNode { pub struct ProtoNode {
pub construction_args: ConstructionArgs, pub construction_args: ConstructionArgs,
pub input: ProtoNodeInput, pub call_argument: Type,
pub identifier: ProtoNodeIdentifier, pub identifier: ProtoNodeIdentifier,
pub original_location: OriginalLocation, pub original_location: OriginalLocation,
pub skip_deduplication: bool, pub skip_deduplication: bool,
@@ -144,45 +139,13 @@ impl Default for ProtoNode {
Self { Self {
identifier: ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"), identifier: ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()), construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()),
input: ProtoNodeInput::None, call_argument: concrete!(()),
original_location: OriginalLocation::default(), original_location: OriginalLocation::default(),
skip_deduplication: false, skip_deduplication: false,
} }
} }
} }
/// Similar to the document node's [`crate::document::NodeInput`].
#[derive(Debug, PartialEq, Eq, Clone, Hash, serde::Serialize, serde::Deserialize)]
pub enum ProtoNodeInput {
/// This input will be converted to `()` as the call argument.
None,
/// A ManualComposition input represents an input that opts out of being resolved through the `ComposeNode`, which first runs the previous (upstream) node, then passes that evaluated
/// result to this node. Instead, ManualComposition lets this node actually consume the provided input instead of passing it to its predecessor.
///
/// Say we have the network `a -> b -> c` where `c` is the output node and `a` is the input node.
/// We would expect `a` to get input from the network, `b` to get input from `a`, and `c` to get input from `b`.
/// This could be represented as `f(x) = c(b(a(x)))`. `a` is run with input `x` from the network. `b` is run with input from `a`. `c` is run with input from `b`.
///
/// However if `b`'s input is using manual composition, this means it would instead be `f(x) = c(b(x))`. This means that `b` actually gets input from the network, and `a` is not automatically
/// executed as it would be using the default ComposeNode flow. Now `b` can use its own logic to decide when or if it wants to run `a` and how to use its output. For example, the CacheNode can
/// look up `x` in its cache and return the result, or otherwise call `a`, cache the result, and return it.
ManualComposition(Type),
/// The previous node where automatic (not manual) composition occurs when compiled. The entire network, of which the node is the output, is fed as input.
///
/// Grayscale example:
///
/// We're interested in receiving an input of the desaturated image data which has been fed through a grayscale filter.
/// (If we were interested in the grayscale filter itself, we would use the `NodeLambda` variant.)
Node(NodeId),
/// Unlike the `Node` variant, with `NodeLambda` we treat the connected node singularly as a lambda node while ignoring all nodes which feed into it from upstream.
///
/// Grayscale example:
///
/// We're interested in receiving an input of a particular image filter, such as a grayscale filter in the form of a grayscale node lambda.
/// (If we were interested in some image data that had been fed through a grayscale filter, we would use the `Node` variant.)
NodeLambda(NodeId),
}
impl ProtoNode { impl ProtoNode {
/// A stable node ID is a hash of a node that should stay constant. This is used in order to remove duplicates from the graph. /// A stable node ID is a hash of a node that should stay constant. This is used in order to remove duplicates from the graph.
/// In the case of `skip_deduplication`, the `document_node_path` is also hashed in order to avoid duplicate monitor nodes from being removed (which would make it impossible to load thumbnails). /// In the case of `skip_deduplication`, the `document_node_path` is also hashed in order to avoid duplicate monitor nodes from being removed (which would make it impossible to load thumbnails).
@@ -196,15 +159,8 @@ impl ProtoNode {
self.original_location.path.hash(&mut hasher); self.original_location.path.hash(&mut hasher);
} }
std::mem::discriminant(&self.input).hash(&mut hasher); std::mem::discriminant(&self.call_argument).hash(&mut hasher);
match self.input { self.call_argument.hash(&mut hasher);
ProtoNodeInput::None => (),
ProtoNodeInput::ManualComposition(ref ty) => {
ty.hash(&mut hasher);
}
ProtoNodeInput::Node(id) => (id, false).hash(&mut hasher),
ProtoNodeInput::NodeLambda(id) => (id, true).hash(&mut hasher),
};
Some(NodeId(hasher.finish())) Some(NodeId(hasher.finish()))
} }
@@ -218,7 +174,7 @@ impl ProtoNode {
Self { Self {
identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"), identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"),
construction_args: value, construction_args: value,
input: ProtoNodeInput::ManualComposition(concrete!(Context)), call_argument: concrete!(Context),
original_location: OriginalLocation { original_location: OriginalLocation {
path: Some(path), path: Some(path),
inputs_exposed: vec![false; inputs_exposed], inputs_exposed: vec![false; inputs_exposed],
@@ -230,23 +186,13 @@ impl ProtoNode {
/// Converts all references to other node IDs into new IDs by running the specified function on them. /// Converts all references to other node IDs into new IDs by running the specified function on them.
/// This can be used when changing the IDs of the nodes, for example in the case of generating stable IDs. /// This can be used when changing the IDs of the nodes, for example in the case of generating stable IDs.
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId, skip_lambdas: bool) { pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {
match self.input {
ProtoNodeInput::Node(id) => self.input = ProtoNodeInput::Node(f(id)),
ProtoNodeInput::NodeLambda(id) => {
if !skip_lambdas {
self.input = ProtoNodeInput::NodeLambda(f(id))
}
}
_ => (),
}
if let ConstructionArgs::Nodes(ids) = &mut self.construction_args { if let ConstructionArgs::Nodes(ids) = &mut self.construction_args {
ids.iter_mut().filter(|(_, lambda)| !(skip_lambdas && *lambda)).for_each(|(id, _)| *id = f(*id)); ids.iter_mut().for_each(|id| *id = f(*id));
} }
} }
pub fn unwrap_construction_nodes(&self) -> Vec<(NodeId, bool)> { pub fn unwrap_construction_nodes(&self) -> Vec<NodeId> {
match &self.construction_args { match &self.construction_args {
ConstructionArgs::Nodes(nodes) => nodes.clone(), ConstructionArgs::Nodes(nodes) => nodes.clone(),
_ => panic!("tried to unwrap nodes from non node construction args \n node: {self:#?}"), _ => panic!("tried to unwrap nodes from non node construction args \n node: {self:#?}"),
@@ -285,16 +231,8 @@ impl ProtoNetwork {
pub fn collect_outwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> { pub fn collect_outwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new(); let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for (id, node) in &self.nodes { for (id, node) in &self.nodes {
match &node.input {
ProtoNodeInput::Node(ref_id) | ProtoNodeInput::NodeLambda(ref_id) => {
self.check_ref(ref_id, id);
edges.entry(*ref_id).or_default().push(*id)
}
_ => (),
}
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args { if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
for (ref_id, _) in ref_nodes { for ref_id in ref_nodes {
self.check_ref(ref_id, id); self.check_ref(ref_id, id);
edges.entry(*ref_id).or_default().push(*id) edges.entry(*ref_id).or_default().push(*id)
} }
@@ -313,7 +251,7 @@ impl ProtoNetwork {
let Some(sni) = self.nodes[index].1.stable_node_id() else { let Some(sni) = self.nodes[index].1.stable_node_id() else {
panic!("failed to generate stable node id for node {:#?}", self.nodes[index].1); panic!("failed to generate stable node id for node {:#?}", self.nodes[index].1);
}; };
self.replace_node_id(&outwards_edges, NodeId(index as u64), sni, false); self.replace_node_id(&outwards_edges, NodeId(index as u64), sni);
self.nodes[index].0 = sni; self.nodes[index].0 = sni;
} }
} }
@@ -323,16 +261,8 @@ impl ProtoNetwork {
pub fn collect_inwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> { pub fn collect_inwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new(); let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for (id, node) in &self.nodes { for (id, node) in &self.nodes {
match &node.input {
ProtoNodeInput::Node(ref_id) | ProtoNodeInput::NodeLambda(ref_id) => {
self.check_ref(ref_id, id);
edges.entry(*id).or_default().push(*ref_id)
}
_ => (),
}
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args { if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
for (ref_id, _) in ref_nodes { for ref_id in ref_nodes {
self.check_ref(ref_id, id); self.check_ref(ref_id, id);
edges.entry(*id).or_default().push(*ref_id) edges.entry(*id).or_default().push(*ref_id)
} }
@@ -348,16 +278,9 @@ impl ProtoNetwork {
let mut inwards_edges = vec![Vec::new(); self.nodes.len()]; let mut inwards_edges = vec![Vec::new(); self.nodes.len()];
for (node_id, node) in &self.nodes { for (node_id, node) in &self.nodes {
let node_index = id_map[node_id]; let node_index = id_map[node_id];
match &node.input {
ProtoNodeInput::Node(ref_id) | ProtoNodeInput::NodeLambda(ref_id) => {
self.check_ref(ref_id, &NodeId(node_index as u64));
inwards_edges[node_index].push(id_map[ref_id]);
}
_ => {}
}
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args { if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
for (ref_id, _) in ref_nodes { for ref_id in ref_nodes {
self.check_ref(ref_id, &NodeId(node_index as u64)); self.check_ref(ref_id, &NodeId(node_index as u64));
inwards_edges[node_index].push(id_map[ref_id]); inwards_edges[node_index].push(id_map[ref_id]);
} }
@@ -367,70 +290,31 @@ impl ProtoNetwork {
(inwards_edges, id_map) (inwards_edges, id_map)
} }
/// Inserts a [`structural::ComposeNode`] for each node that has a [`ProtoNodeInput::Node`]. The compose node evaluates the first node, and then sends the result into the second node. /// Performs topological sort and reorders ids.
pub fn resolve_inputs(&mut self) -> Result<(), String> { pub fn resolve_inputs(&mut self) -> Result<(), String> {
// Perform topological sort once // Perform topological sort once
self.reorder_ids()?; self.reorder_ids()?;
let max_id = self.nodes.len() as u64 - 1;
// Collect outward edges once
let outwards_edges = self.collect_outwards_edges();
// Iterate over nodes in topological order
for node_id in 0..=max_id {
let node_id = NodeId(node_id);
let (_, node) = &mut self.nodes[node_id.0 as usize];
if let ProtoNodeInput::Node(input_node_id) = node.input {
// Create a new node that composes the current node and its input node
let compose_node_id = NodeId(self.nodes.len() as u64);
let (_, input_node_id_proto) = &self.nodes[input_node_id.0 as usize];
let input = input_node_id_proto.input.clone();
let mut path = input_node_id_proto.original_location.path.clone();
if let Some(path) = &mut path {
path.push(node_id);
}
self.nodes.push((
compose_node_id,
ProtoNode {
identifier: ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode"),
construction_args: ConstructionArgs::Nodes(vec![(input_node_id, false), (node_id, true)]),
input,
original_location: OriginalLocation { path, ..Default::default() },
skip_deduplication: false,
},
));
self.replace_node_id(&outwards_edges, node_id, compose_node_id, true);
}
}
self.reorder_ids()?;
Ok(()) Ok(())
} }
/// Update all of the references to a node ID in the graph with a new ID named `compose_node_id`. /// Update all of the references to a node ID in the graph with a new ID named `replacement_node_id`.
fn replace_node_id(&mut self, outwards_edges: &HashMap<NodeId, Vec<NodeId>>, node_id: NodeId, compose_node_id: NodeId, skip_lambdas: bool) { fn replace_node_id(&mut self, outwards_edges: &HashMap<NodeId, Vec<NodeId>>, node_id: NodeId, replacement_node_id: NodeId) {
// Update references in other nodes to use the new compose node // Update references in other nodes to use the new node
if let Some(referring_nodes) = outwards_edges.get(&node_id) { if let Some(referring_nodes) = outwards_edges.get(&node_id) {
for &referring_node_id in referring_nodes { for &referring_node_id in referring_nodes {
let (_, referring_node) = &mut self.nodes[referring_node_id.0 as usize]; let (_, referring_node) = &mut self.nodes[referring_node_id.0 as usize];
referring_node.map_ids(|id| if id == node_id { compose_node_id } else { id }, skip_lambdas) referring_node.map_ids(|id| if id == node_id { replacement_node_id } else { id })
} }
} }
if self.output == node_id { if self.output == node_id {
self.output = compose_node_id; self.output = replacement_node_id;
} }
self.inputs.iter_mut().for_each(|id| { self.inputs.iter_mut().for_each(|id| {
if *id == node_id { if *id == node_id {
*id = compose_node_id; *id = replacement_node_id;
} }
}); });
} }
@@ -508,7 +392,7 @@ impl ProtoNetwork {
for (index, &id) in order.iter().enumerate() { for (index, &id) in order.iter().enumerate() {
let mut node = std::mem::take(&mut self.nodes[id.0 as usize].1); let mut node = std::mem::take(&mut self.nodes[id.0 as usize].1);
// Update node references to reflect the new order // Update node references to reflect the new order
node.map_ids(|id| NodeId(*new_positions.get(&id).expect("node not found in lookup table") as u64), false); node.map_ids(|id| NodeId(*new_positions.get(&id).expect("node not found in lookup table") as u64));
new_nodes.push((NodeId(index as u64), node)); new_nodes.push((NodeId(index as u64), node));
} }
@@ -661,7 +545,6 @@ impl TypingContext {
let inputs = match node.construction_args { let inputs = match node.construction_args {
// If the node has a value input we can infer the return type from it // If the node has a value input we can infer the return type from it
ConstructionArgs::Value(ref v) => { 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 // 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.ty())), vec![]);
self.inferred.insert(node_id, types.clone()); self.inferred.insert(node_id, types.clone());
@@ -670,7 +553,7 @@ impl TypingContext {
// If the node has nodes as inputs we can infer the types from the node outputs // If the node has nodes as inputs we can infer the types from the node outputs
ConstructionArgs::Nodes(ref nodes) => nodes ConstructionArgs::Nodes(ref nodes) => nodes
.iter() .iter()
.map(|(id, _)| { .map(|id| {
self.inferred self.inferred
.get(id) .get(id)
.ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NodeNotFound(*id))]) .ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NodeNotFound(*id))])
@@ -681,16 +564,7 @@ impl TypingContext {
}; };
// Get the node input type from the proto node declaration // Get the node input type from the proto node declaration
// TODO: When removing automatic composition, rename this to just `call_argument` let call_argument = &node.call_argument;
let primary_input_or_call_argument = match node.input {
ProtoNodeInput::None => concrete!(()),
ProtoNodeInput::ManualComposition(ref ty) => ty.clone(),
ProtoNodeInput::Node(id) | ProtoNodeInput::NodeLambda(id) => {
let input = self.inferred.get(&id).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::InputNodeNotFound(id))])?;
input.return_value.clone()
}
};
let using_manual_composition = matches!(node.input, ProtoNodeInput::ManualComposition(_) | ProtoNodeInput::None);
let impls = self.lookup.get(&node.identifier).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?; let impls = self.lookup.get(&node.identifier).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?;
if let Some(index) = inputs.iter().position(|p| { if let Some(index) = inputs.iter().position(|p| {
@@ -732,7 +606,7 @@ impl TypingContext {
// List of all implementations that match the input types // List of all implementations that match the input types
let valid_output_types = impls let valid_output_types = impls
.keys() .keys()
.filter(|node_io| valid_type(&node_io.call_argument, &primary_input_or_call_argument) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_type(p1, p2))) .filter(|node_io| valid_type(&node_io.call_argument, call_argument) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_type(p1, p2)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Attempt to substitute generic types with concrete types and save the list of results // Attempt to substitute generic types with concrete types and save the list of results
@@ -741,7 +615,7 @@ impl TypingContext {
.map(|node_io| { .map(|node_io| {
let generics_lookup: Result<HashMap<_, _>, _> = collect_generics(node_io) let generics_lookup: Result<HashMap<_, _>, _> = collect_generics(node_io)
.iter() .iter()
.map(|generic| check_generic(node_io, &primary_input_or_call_argument, &inputs, generic).map(|x| (generic.to_string(), x))) .map(|generic| check_generic(node_io, call_argument, &inputs, generic).map(|x| (generic.to_string(), x)))
.collect(); .collect();
generics_lookup.map(|generics_lookup| { generics_lookup.map(|generics_lookup| {
@@ -761,7 +635,7 @@ impl TypingContext {
let mut best_errors = usize::MAX; let mut best_errors = usize::MAX;
let mut error_inputs = Vec::new(); let mut error_inputs = Vec::new();
for node_io in impls.keys() { for node_io in impls.keys() {
let current_errors = [&primary_input_or_call_argument] let current_errors = [call_argument]
.into_iter() .into_iter()
.chain(&inputs) .chain(&inputs)
.cloned() .cloned()
@@ -770,7 +644,6 @@ impl TypingContext {
.filter(|(_, (p1, p2))| !valid_type(p1, p2)) .filter(|(_, (p1, p2))| !valid_type(p1, p2))
.map(|(index, ty)| { .map(|(index, ty)| {
let i = node.original_location.inputs(index).min_by_key(|s| s.node.len()).map(|s| s.index).unwrap_or(index); let i = node.original_location.inputs(index).min_by_key(|s| s.node.len()).map(|s| s.index).unwrap_or(index);
let i = if using_manual_composition { i } else { i + 1 };
(i, ty) (i, ty)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -782,15 +655,11 @@ impl TypingContext {
error_inputs.push(current_errors); error_inputs.push(current_errors);
} }
} }
let inputs = [&primary_input_or_call_argument] let inputs = [call_argument]
.into_iter() .into_iter()
.chain(&inputs) .chain(&inputs)
.enumerate() .enumerate()
// TODO: Make the following line's if statement conditional on being a call argument or primary input .filter_map(|(i, t)| if i == 0 { None } else { Some(format!("• Input {i}: {t}")) })
.filter_map(|(i, t)| {
let i = if using_manual_composition { i } else { i + 1 };
if i == 0 { None } else { Some(format!("• Input {i}: {t}")) }
})
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .join("\n");
Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })]) Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })])
@@ -817,13 +686,13 @@ impl TypingContext {
return Ok(node_io.clone()); return Ok(node_io.clone());
} }
} }
let inputs = [&primary_input_or_call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", "); let inputs = [call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let valid = valid_output_types.into_iter().cloned().collect(); let valid = valid_output_types.into_iter().cloned().collect();
Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })]) Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })])
} }
_ => { _ => {
let inputs = [&primary_input_or_call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", "); let inputs = [call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
let valid = valid_output_types.into_iter().cloned().collect(); let valid = valid_output_types.into_iter().cloned().collect();
Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })]) Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })])
} }
@@ -882,7 +751,7 @@ fn replace_generics(types: &mut NodeIOTypes, lookup: &HashMap<String, Type>) {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode};
#[test] #[test]
fn topological_sort() { fn topological_sort() {
@@ -929,16 +798,6 @@ mod test {
assert_eq!(ids, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]); assert_eq!(ids, vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)]);
} }
#[test]
fn input_resolution() {
let mut construction_network = test_network();
construction_network.resolve_inputs().expect("Error when calling 'resolve_inputs' on 'construction_network.");
println!("{construction_network:#?}");
assert_eq!(construction_network.nodes[0].1.identifier.name.as_ref(), "value");
assert_eq!(construction_network.nodes.len(), 6);
assert_eq!(construction_network.nodes[5].1.construction_args, ConstructionArgs::Nodes(vec![(NodeId(3), false), (NodeId(4), true)]));
}
#[test] #[test]
fn stable_node_id_generation() { fn stable_node_id_generation() {
let mut construction_network = test_network(); let mut construction_network = test_network();
@@ -948,14 +807,7 @@ mod test {
let ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect(); let ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect();
assert_eq!( assert_eq!(
ids, ids,
vec![ vec![NodeId(13743208144182721472), NodeId(4607569396187877965), NodeId(16950305885390329527), NodeId(15151181027373658932)]
NodeId(16997244687192517417),
NodeId(12226224850522777131),
NodeId(9162113827627229771),
NodeId(12793582657066318419),
NodeId(16945623684036608820),
NodeId(2640415155091892458)
]
); );
} }
@@ -968,8 +820,8 @@ mod test {
NodeId(7), NodeId(7),
ProtoNode { ProtoNode {
identifier: "id".into(), identifier: "id".into(),
input: ProtoNodeInput::Node(NodeId(11)), call_argument: concrete!(()),
construction_args: ConstructionArgs::Nodes(vec![]), construction_args: ConstructionArgs::Nodes(vec![NodeId(11)]),
..Default::default() ..Default::default()
}, },
), ),
@@ -977,8 +829,8 @@ mod test {
NodeId(1), NodeId(1),
ProtoNode { ProtoNode {
identifier: "id".into(), identifier: "id".into(),
input: ProtoNodeInput::Node(NodeId(11)), call_argument: concrete!(()),
construction_args: ConstructionArgs::Nodes(vec![]), construction_args: ConstructionArgs::Nodes(vec![NodeId(11)]),
..Default::default() ..Default::default()
}, },
), ),
@@ -986,8 +838,8 @@ mod test {
NodeId(10), NodeId(10),
ProtoNode { ProtoNode {
identifier: "cons".into(), identifier: "cons".into(),
input: ProtoNodeInput::ManualComposition(concrete!(u32)), call_argument: concrete!(u32),
construction_args: ConstructionArgs::Nodes(vec![(NodeId(14), false)]), construction_args: ConstructionArgs::Nodes(vec![NodeId(14)]),
..Default::default() ..Default::default()
}, },
), ),
@@ -995,8 +847,8 @@ mod test {
NodeId(11), NodeId(11),
ProtoNode { ProtoNode {
identifier: "add".into(), identifier: "add".into(),
input: ProtoNodeInput::Node(NodeId(10)), call_argument: concrete!(()),
construction_args: ConstructionArgs::Nodes(vec![]), construction_args: ConstructionArgs::Nodes(vec![NodeId(10)]),
..Default::default() ..Default::default()
}, },
), ),
@@ -1004,7 +856,7 @@ mod test {
NodeId(14), NodeId(14),
ProtoNode { ProtoNode {
identifier: "value".into(), identifier: "value".into(),
input: ProtoNodeInput::None, call_argument: concrete!(()),
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(2).into()), construction_args: ConstructionArgs::Value(value::TaggedValue::U32(2).into()),
..Default::default() ..Default::default()
}, },
@@ -1024,8 +876,8 @@ mod test {
NodeId(1), NodeId(1),
ProtoNode { ProtoNode {
identifier: "id".into(), identifier: "id".into(),
input: ProtoNodeInput::Node(NodeId(2)), call_argument: concrete!(()),
construction_args: ConstructionArgs::Nodes(vec![]), construction_args: ConstructionArgs::Nodes(vec![NodeId(2)]),
..Default::default() ..Default::default()
}, },
), ),
@@ -1033,8 +885,8 @@ mod test {
NodeId(2), NodeId(2),
ProtoNode { ProtoNode {
identifier: "id".into(), identifier: "id".into(),
input: ProtoNodeInput::Node(NodeId(1)), call_argument: concrete!(()),
construction_args: ConstructionArgs::Nodes(vec![]), construction_args: ConstructionArgs::Nodes(vec![NodeId(1)]),
..Default::default() ..Default::default()
}, },
), ),
+3 -6
View File
@@ -88,10 +88,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
} }
let device = application_io.gpu_executor().unwrap().context.device.clone(); let device = application_io.gpu_executor().unwrap().context.device.clone();
let preferences = EditorPreferences { let preferences = EditorPreferences { use_vello: true };
use_vello: true,
..Default::default()
};
let editor_api = Arc::new(WasmEditorApi { let editor_api = Arc::new(WasmEditorApi {
font_cache: FontCache::default(), font_cache: FontCache::default(),
application_io: Some(application_io.into()), application_io: Some(application_io.into()),
@@ -104,7 +101,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
match app.command { match app.command {
Command::Compile { print_proto, .. } => { Command::Compile { print_proto, .. } => {
if print_proto { if print_proto {
println!("{}", proto_graph); println!("{proto_graph}");
} }
} }
Command::Run { run_loop, .. } => { Command::Run { run_loop, .. } => {
@@ -120,7 +117,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
loop { loop {
let result = (&executor).execute(render_config).await?; let result = (&executor).execute(render_config).await?;
if !run_loop { if !run_loop {
println!("{:?}", result); println!("{result:?}");
break; break;
} }
tokio::time::sleep(std::time::Duration::from_millis(16)).await; tokio::time::sleep(std::time::Duration::from_millis(16)).await;
+1 -22
View File
@@ -1,6 +1,6 @@
use dyn_any::StaticType; use dyn_any::StaticType;
pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode}; pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer}; use graph_craft::proto::{FutureAny, SharedNodeContainer};
use graphene_core::NodeIO; use graphene_core::NodeIO;
use graphene_core::WasmNotSend; use graphene_core::WasmNotSend;
pub use graphene_core::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode}; pub use graphene_core::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode};
@@ -19,27 +19,6 @@ where
} }
} }
pub struct ComposeTypeErased {
first: SharedNodeContainer,
second: SharedNodeContainer,
}
impl<'i> Node<'i, Any<'i>> for ComposeTypeErased {
type Output = DynFuture<'i, Any<'i>>;
fn eval(&'i self, input: Any<'i>) -> Self::Output {
Box::pin(async move {
let arg = self.first.eval(input).await;
self.second.eval(arg).await
})
}
}
impl ComposeTypeErased {
pub const fn new(first: SharedNodeContainer, second: SharedNodeContainer) -> Self {
ComposeTypeErased { first, second }
}
}
pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(), O> { pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(), O> {
downcast_node(n) downcast_node(n)
} }
+1 -1
View File
@@ -1209,7 +1209,7 @@ impl Render for Table<Raster<CPU>> {
} }
} }
const LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new())); static LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new()));
impl Render for Table<Raster<GPU>> { impl Render for Table<Raster<GPU>> {
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) { fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
@@ -150,8 +150,8 @@ pub enum IntrospectError {
impl std::fmt::Display for IntrospectError { impl std::fmt::Display for IntrospectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
IntrospectError::PathNotFound(path) => write!(f, "Path not found: {:?}", path), IntrospectError::PathNotFound(path) => write!(f, "Path not found: {path:?}"),
IntrospectError::ProtoNodeNotFound(id) => write!(f, "ProtoNode not found: {:?}", id), IntrospectError::ProtoNodeNotFound(id) => write!(f, "ProtoNode not found: {id:?}"),
IntrospectError::NoData => write!(f, "No data found for this node"), IntrospectError::NoData => write!(f, "No data found for this node"),
IntrospectError::RuntimeNotReady => write!(f, "Node runtime is not ready"), IntrospectError::RuntimeNotReady => write!(f, "Node runtime is not ready"),
} }
@@ -278,24 +278,24 @@ impl BorrowTree {
/// let (proto_network, node_id, proto_node) = ProtoNetwork::example(); /// let (proto_network, node_id, proto_node) = ProtoNetwork::example();
/// let typing_context = TypingContext::new(&node_registry::NODE_REGISTRY); /// let typing_context = TypingContext::new(&node_registry::NODE_REGISTRY);
/// let mut borrow_tree = BorrowTree::new(proto_network, &typing_context).await?; /// let mut borrow_tree = BorrowTree::new(proto_network, &typing_context).await?;
/// ///
/// // Assert that the node exists in the BorrowTree /// // Assert that the node exists in the BorrowTree
/// assert!(borrow_tree.get(node_id).is_some(), "Node should exist before removal"); /// assert!(borrow_tree.get(node_id).is_some(), "Node should exist before removal");
/// ///
/// // Remove the node /// // Remove the node
/// let removed_path = borrow_tree.free_node(node_id); /// let removed_path = borrow_tree.free_node(node_id);
/// ///
/// // Assert that the node was successfully removed /// // Assert that the node was successfully removed
/// assert!(removed_path.is_some(), "Node removal should return a path"); /// assert!(removed_path.is_some(), "Node removal should return a path");
/// assert!(borrow_tree.get(node_id).is_none(), "Node should not exist after removal"); /// assert!(borrow_tree.get(node_id).is_none(), "Node should not exist after removal");
/// ///
/// // Try to remove the same node again /// // Try to remove the same node again
/// let second_removal = borrow_tree.free_node(node_id); /// let second_removal = borrow_tree.free_node(node_id);
/// ///
/// assert_eq!(second_removal, None, "Second removal should return None"); /// assert_eq!(second_removal, None, "Second removal should return None");
/// ///
/// println!("All assertions passed. free_node function works as expected."); /// println!("All assertions passed. free_node function works as expected.");
/// ///
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
@@ -393,7 +393,7 @@ impl BorrowTree {
} }
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
ConstructionArgs::Nodes(ids) => { ConstructionArgs::Nodes(ids) => {
let ids: Vec<_> = ids.iter().map(|(id, _)| *id).collect(); let ids = ids.to_vec();
let construction_nodes = self.node_deps(&ids); let construction_nodes = self.node_deps(&ids);
let constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?; let constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let node = constructor(construction_nodes).await; let node = constructor(construction_nodes).await;
+2 -1
View File
@@ -19,7 +19,8 @@ mod tests {
( (
NodeId(0), NodeId(0),
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0)], inputs: vec![],
call_argument: concrete!(u32),
implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER),
..Default::default() ..Default::default()
}, },
@@ -8,15 +8,15 @@ use graphene_core::raster::*;
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
use graphene_core::raster_types::GPU; use graphene_core::raster_types::GPU;
use graphene_core::raster_types::{CPU, Raster}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::{Artboard, concrete, generic}; use graphene_core::{Artboard, concrete};
use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Cow, ProtoNodeIdentifier};
use graphene_core::{NodeIO, NodeIOTypes}; use graphene_core::{NodeIO, NodeIOTypes};
use graphene_core::{fn_type_fut, future}; use graphene_core::{fn_type_fut, future};
use graphene_std::Context; use graphene_std::Context;
use graphene_std::Graphic; use graphene_std::Graphic;
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
use graphene_std::any::DowncastBothNode; use graphene_std::any::DowncastBothNode;
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode}; use graphene_std::any::DynAnyNode;
use graphene_std::application_io::{ImageTexture, SurfaceFrame}; use graphene_std::application_io::{ImageTexture, SurfaceFrame};
use graphene_std::brush::brush_cache::BrushCache; use graphene_std::brush::brush_cache::BrushCache;
use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::brush::brush_stroke::BrushStroke;
@@ -161,25 +161,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
// ============
// COMPOSE NODE
// ============
(
ProtoNodeIdentifier::new("graphene_core::structural::ComposeNode"),
|args| {
Box::pin(async move {
let node = ComposeTypeErased::new(args[0].clone(), args[1].clone());
node.into_type_erased()
})
},
// This is how we can generically define composition of two nodes.
// See further details in the code definition for the `struct ComposeNode<First, Second, I> { ... }` struct.
NodeIOTypes::new(
generic!(T),
generic!(U),
vec![Type::Fn(Box::new(generic!(T)), Box::new(generic!(V))), Type::Fn(Box::new(generic!(V)), Box::new(generic!(U)))],
),
),
// ======================= // =======================
// CREATE GPU SURFACE NODE // CREATE GPU SURFACE NODE
// ======================= // =======================
@@ -294,17 +275,23 @@ mod node_registry_macros {
(from: $from:ty, to: $to:ty) => { (from: $from:ty, to: $to:ty) => {
( (
ProtoNodeIdentifier::new(concat!["graphene_core::ops::IntoNode<", stringify!($to), ">"]), ProtoNodeIdentifier::new(concat!["graphene_core::ops::IntoNode<", stringify!($to), ">"]),
|_| { |mut args| {
Box::pin(async move { Box::pin(async move {
let node = graphene_core::ops::IntoNode::<$to>::new(); let node = graphene_core::ops::IntoNode::new(
let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node); graphene_std::any::downcast_node::<Context, $from>(args.pop().unwrap()),
graphene_std::any::FutureWrapperNode::new(graphene_std::value::ClonedNode::new(std::marker::PhantomData::<$to>)),
);
let any: DynAnyNode<Context, $to, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox Box::new(any) as TypeErasedBox
}) })
}, },
{ {
let node = graphene_core::ops::IntoNode::<$to>::new(); let node = graphene_core::ops::IntoNode::new(
let mut node_io = NodeIO::<'_, $from>::to_async_node_io(&node, vec![]); graphene_std::any::PanicNode::<Context, core::pin::Pin<Box<dyn core::future::Future<Output = $from> + Send>>>::new(),
node_io.call_argument = future!(<$from as StaticType>::Static); graphene_std::any::FutureWrapperNode::new(graphene_std::value::ClonedNode::new(std::marker::PhantomData::<$to>)),
);
let params = vec![fn_type_fut!(Context, $from)];
let node_io = NodeIO::<'_, Context>::to_async_node_io(&node, params);
node_io node_io
}, },
) )
@@ -333,17 +320,20 @@ mod node_registry_macros {
(from: $from:ty, to: $to:ty) => { (from: $from:ty, to: $to:ty) => {
( (
ProtoNodeIdentifier::new(concat!["graphene_core::ops::ConvertNode<", stringify!($to), ">"]), ProtoNodeIdentifier::new(concat!["graphene_core::ops::ConvertNode<", stringify!($to), ">"]),
|_| { |mut args| {
Box::pin(async move { Box::pin(async move {
let node = graphene_core::ops::ConvertNode::<$to>::new(); let node = graphene_core::ops::ConvertNode::new(graphene_std::any::downcast_node::<Context, $from>(args.pop().unwrap()),
let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node); graphene_std::any::FutureWrapperNode::new(graphene_std::value::ClonedNode::new(std::marker::PhantomData::<$to>)) );
let any: DynAnyNode<Context, $to, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox Box::new(any) as TypeErasedBox
}) })
}, },
{ {
let node = graphene_core::ops::ConvertNode::<$to>::new(); let node = graphene_core::ops::ConvertNode::new(graphene_std::any::PanicNode::<Context, core::pin::Pin<Box<dyn core::future::Future<Output = $from> + Send>>>::new(),
let mut node_io = NodeIO::<'_, $from>::to_async_node_io(&node, vec![]);
node_io.call_argument = future!(<$from as StaticType>::Static); graphene_std::any::FutureWrapperNode::new(graphene_std::value::ClonedNode::new(std::marker::PhantomData::<$to>)) );
let params = vec![fn_type_fut!(Context, $from)];
let node_io = NodeIO::<'_, Context>::to_async_node_io(&node, params);
node_io node_io
}, },
) )
+3 -3
View File
@@ -30,20 +30,20 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::scope("editor-api")], inputs: vec![NodeInput::scope("editor-api")],
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), call_argument: concrete!(Context),
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memo::IDENTIFIER),
..Default::default() ..Default::default()
}, },
// TODO: Add conversion step // TODO: Add conversion step
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(graphene_std::application_io::RenderConfig)), call_argument: concrete!(graphene_std::application_io::RenderConfig),
inputs: vec![ inputs: vec![
NodeInput::scope("editor-api"), NodeInput::scope("editor-api"),
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(generic!(T))), 0), NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(generic!(T))), 0),
+2 -1
View File
@@ -293,7 +293,7 @@ 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 properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));
let cfg = crate::shader_nodes::modify_cfg(&attributes); let cfg = crate::shader_nodes::modify_cfg(attributes);
let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, &graphene_core, &identifier, &cfg); let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, &graphene_core, &identifier, &cfg);
Ok(quote! { Ok(quote! {
/// Underlying implementation for [#struct_name] /// Underlying implementation for [#struct_name]
@@ -323,6 +323,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
#cfg #cfg
#[doc(hidden)] #[doc(hidden)]
#[allow(clippy::module_inception)]
mod #mod_name { mod #mod_name {
use super::*; use super::*;
use #graphene_core as gcore; use #graphene_core as gcore;
@@ -117,7 +117,7 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
.map_err(|e| { .map_err(|e| {
syn::Error::new( syn::Error::new(
Span::call_site(), Span::call_site(),
format!("Failed to find location of 'graphene_core' or 'graphene-core-shaders'. Make sure it is imported as a dependency: {}", e), format!("Failed to find location of 'graphene_core' or 'graphene-core-shaders'. Make sure it is imported as a dependency: {e}"),
) )
})?; })?;
match crate_name { match crate_name {
+21 -30
View File
@@ -295,8 +295,8 @@ impl Parse for NodeFnAttributes {
} }
fn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<ParsedNodeFn> { fn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<ParsedNodeFn> {
let attributes = syn::parse2::<NodeFnAttributes>(attr.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse node_fn attributes: {}", e)))?; let attributes = syn::parse2::<NodeFnAttributes>(attr.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse node_fn attributes: {e}")))?;
let input_fn = syn::parse2::<ItemFn>(item.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse function: {}. Make sure it's a valid Rust function.", e)))?; let input_fn = syn::parse2::<ItemFn>(item.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse function: {e}. Make sure it's a valid Rust function.")))?;
let vis = input_fn.vis; let vis = input_fn.vis;
let fn_name = input_fn.sig.ident.clone(); let fn_name = input_fn.sig.ident.clone();
@@ -312,7 +312,7 @@ fn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<ParsedNo
let crate_name = proc_macro_crate::crate_name("graphene-core").map_err(|e| { let crate_name = proc_macro_crate::crate_name("graphene-core").map_err(|e| {
Error::new( Error::new(
proc_macro2::Span::call_site(), proc_macro2::Span::call_site(),
format!("Failed to find location of graphene_core. Make sure it is imported as a dependency: {}", e), format!("Failed to find location of graphene_core. Make sure it is imported as a dependency: {e}"),
) )
})?; })?;
let description = input_fn let description = input_fn
@@ -405,7 +405,7 @@ fn parse_implementations(attr: &Attribute, name: &Ident) -> syn::Result<Punctuat
let parser = Punctuated::<Type, Comma>::parse_terminated; let parser = Punctuated::<Type, Comma>::parse_terminated;
parser.parse2(content.clone()).map_err(|e| { parser.parse2(content.clone()).map_err(|e| {
let span = e.span(); // Get the span of the error let span = e.span(); // Get the span of the error
Error::new(span, format!("Failed to parse implementations for argument '{}': {}", name, e)) Error::new(span, format!("Failed to parse implementations for argument '{name}': {e}"))
}) })
} }
@@ -431,27 +431,21 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
let ident = &pat_ident.ident; let ident = &pat_ident.ident;
let default_value = extract_attribute(attrs, "default") let default_value = extract_attribute(attrs, "default")
.map(|attr| { .map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `default` value for argument '{ident}': {e}"))))
attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid `default` value for argument '{}': {}", ident, e)))
})
.transpose()?; .transpose()?;
let scope = extract_attribute(attrs, "scope") let scope = extract_attribute(attrs, "scope")
.map(|attr| { .map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `scope` value for argument '{ident}': {e}"))))
attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid `scope` value for argument '{}': {}", ident, e)))
})
.transpose()?; .transpose()?;
let name = extract_attribute(attrs, "name") let name = extract_attribute(attrs, "name")
.map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `name` value for argument '{}': {}", ident, e)))) .map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `name` value for argument '{ident}': {e}"))))
.transpose()?; .transpose()?;
let widget_override = extract_attribute(attrs, "widget") let widget_override = extract_attribute(attrs, "widget")
.map(|attr| { .map(|attr| {
attr.parse_args() attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid `widget override` value for argument '{}': {}", ident, e))) .map_err(|e| Error::new_spanned(attr, format!("Invalid `widget override` value for argument '{ident}': {e}")))
}) })
.transpose()? .transpose()?
.unwrap_or_default(); .unwrap_or_default();
@@ -468,26 +462,26 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
let number_soft_min = extract_attribute(attrs, "soft_min") let number_soft_min = extract_attribute(attrs, "soft_min")
.map(|attr| { .map(|attr| {
attr.parse_args() attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `soft_min` value for argument '{}': {}", ident, e))) .map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `soft_min` value for argument '{ident}': {e}")))
}) })
.transpose()?; .transpose()?;
let number_soft_max = extract_attribute(attrs, "soft_max") let number_soft_max = extract_attribute(attrs, "soft_max")
.map(|attr| { .map(|attr| {
attr.parse_args() attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `soft_max` value for argument '{}': {}", ident, e))) .map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `soft_max` value for argument '{ident}': {e}")))
}) })
.transpose()?; .transpose()?;
let number_hard_min = extract_attribute(attrs, "hard_min") let number_hard_min = extract_attribute(attrs, "hard_min")
.map(|attr| { .map(|attr| {
attr.parse_args() attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `hard_min` value for argument '{}': {}", ident, e))) .map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `hard_min` value for argument '{ident}': {e}")))
}) })
.transpose()?; .transpose()?;
let number_hard_max = extract_attribute(attrs, "hard_max") let number_hard_max = extract_attribute(attrs, "hard_max")
.map(|attr| { .map(|attr| {
attr.parse_args() attr.parse_args()
.map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `hard_max` value for argument '{}': {}", ident, e))) .map_err(|e| Error::new_spanned(attr, format!("Invalid numerical `hard_max` value for argument '{ident}': {e}")))
}) })
.transpose()?; .transpose()?;
@@ -496,10 +490,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
attr.parse_args::<ExprTuple>().map_err(|e| { attr.parse_args::<ExprTuple>().map_err(|e| {
Error::new_spanned( Error::new_spanned(
attr, attr,
format!( format!("Invalid `range` tuple of min and max range slider values for argument '{ident}': {e}\nUSAGE EXAMPLE: #[range((0., 100.))]"),
"Invalid `range` tuple of min and max range slider values for argument '{}': {}\nUSAGE EXAMPLE: #[range((0., 100.))]",
ident, e
),
) )
}) })
}) })
@@ -511,7 +502,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
} }
let unit = extract_attribute(attrs, "unit") let unit = extract_attribute(attrs, "unit")
.map(|attr| attr.parse_args::<LitStr>().map_err(|_e| Error::new_spanned(attr, format!("Expected a unit type as string")))) .map(|attr| attr.parse_args::<LitStr>().map_err(|_e| Error::new_spanned(attr, "Expected a unit type as string".to_string())))
.transpose()?; .transpose()?;
let number_display_decimal_places = extract_attribute(attrs, "display_decimal_places") let number_display_decimal_places = extract_attribute(attrs, "display_decimal_places")
@@ -519,14 +510,14 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
attr.parse_args::<LitInt>().map_err(|e| { attr.parse_args::<LitInt>().map_err(|e| {
Error::new_spanned( Error::new_spanned(
attr, attr,
format!("Invalid `integer` for number of decimals for argument '{}': {}\nUSAGE EXAMPLE: #[display_decimal_places(2)]", ident, e), format!("Invalid `integer` for number of decimals for argument '{ident}': {e}\nUSAGE EXAMPLE: #[display_decimal_places(2)]"),
) )
}) })
}) })
.transpose()? .transpose()?
.map(|f| { .map(|f| {
if let Err(e) = f.base10_parse::<u32>() { if let Err(e) = f.base10_parse::<u32>() {
Err(Error::new_spanned(f, format!("Expected a `u32` for `display_decimal_places` for '{}': {}", ident, e))) Err(Error::new_spanned(f, format!("Expected a `u32` for `display_decimal_places` for '{ident}': {e}")))
} else { } else {
Ok(f) Ok(f)
} }
@@ -535,7 +526,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
let number_step = extract_attribute(attrs, "step") let number_step = extract_attribute(attrs, "step")
.map(|attr| { .map(|attr| {
attr.parse_args::<LitFloat>() attr.parse_args::<LitFloat>()
.map_err(|e| Error::new_spanned(attr, format!("Invalid `step` for argument '{}': {}\nUSAGE EXAMPLE: #[step(2.)]", ident, e))) .map_err(|e| Error::new_spanned(attr, format!("Invalid `step` for argument '{ident}': {e}\nUSAGE EXAMPLE: #[step(2.)]")))
}) })
.transpose()?; .transpose()?;
@@ -660,7 +651,7 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
Ok(parsed) => parsed, Ok(parsed) => parsed,
Err(e) => { Err(e) => {
// Return the error as a compile error // Return the error as a compile error
Error::new(e.span(), format!("Failed to parse node function: {}", e)).to_compile_error() Error::new(e.span(), format!("Failed to parse node function: {e}")).to_compile_error()
} }
} }
} }
@@ -757,7 +748,7 @@ mod tests {
} }
_ => panic!("Mismatched default values"), _ => panic!("Mismatched default values"),
} }
assert_eq!(format!("{:?}", p_ty), format!("{:?}", e_ty)); assert_eq!(format!("{p_ty:?}"), format!("{:?}", e_ty));
} }
( (
ParsedField { ParsedField {
@@ -780,8 +771,8 @@ mod tests {
}, },
) => { ) => {
assert_eq!(p_name, e_name); assert_eq!(p_name, e_name);
assert_eq!(format!("{:?}", p_input), format!("{:?}", e_input)); assert_eq!(format!("{p_input:?}"), format!("{:?}", e_input));
assert_eq!(format!("{:?}", p_output), format!("{:?}", e_output)); assert_eq!(format!("{p_output:?}"), format!("{:?}", e_output));
} }
_ => panic!("Mismatched field types"), _ => panic!("Mismatched field types"),
} }
+5 -6
View File
@@ -61,7 +61,7 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
( (
NodeId(i as u64), NodeId(i as u64),
match inputs.len() { match inputs.len() {
1 if false => { 1 => {
let input = inputs.iter().next().unwrap(); let input = inputs.iter().next().unwrap();
let input_ty = input.nested_type(); let input_ty = input.nested_type();
@@ -84,9 +84,9 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(input.clone(), i)], inputs: vec![NodeInput::network(input.clone(), i)],
// manual_composition: Some(fn_input.clone()),
implementation: DocumentNodeImplementation::ProtoNode(proto_node), implementation: DocumentNodeImplementation::ProtoNode(proto_node),
visible: true, visible: true,
call_argument: concrete!(Context),
..Default::default() ..Default::default()
} }
} }
@@ -107,8 +107,8 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
let document_node = DocumentNode { let document_node = DocumentNode {
inputs: network_inputs, inputs: network_inputs,
manual_composition: Some(input_type.clone()), call_argument: input_type.clone(),
implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()), implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
visible: true, visible: true,
skip_deduplication: false, skip_deduplication: false,
..Default::default() ..Default::default()
@@ -118,12 +118,11 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
let node = DocumentNode { let node = DocumentNode {
inputs, inputs,
manual_composition: Some(input_type.clone()), call_argument: input_type.clone(),
implementation: DocumentNodeImplementation::Network(NodeNetwork { implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::Node { exports: vec![NodeInput::Node {
node_id: NodeId(input_count as u64), node_id: NodeId(input_count as u64),
output_index: 0, output_index: 0,
lambda: false,
}], }],
nodes, nodes,
scope_injections: Default::default(), scope_injections: Default::default(),
+5 -8
View File
@@ -101,14 +101,11 @@ pub fn derive_discriminant_impl(input_item: TokenStream) -> syn::Result<TokenStr
let (pattern, value) = is_sub_discriminant let (pattern, value) = is_sub_discriminant
.into_iter() .into_iter()
.map(|b| { .map(|b| {
( if b {
if b { (quote::quote! {(x)}, quote::quote! {(x.to_discriminant())})
quote::quote! { (x) } } else {
} else { (quote::quote! {{..}}, Default::default())
quote::quote! { { .. } } }
},
b.then(|| quote::quote! { (x.to_discriminant()) }).unwrap_or_default(),
)
}) })
.unzip::<_, _, Vec<_>, Vec<_>>(); .unzip::<_, _, Vec<_>, Vec<_>>();
#[cfg(feature = "serde-discriminant")] #[cfg(feature = "serde-discriminant")]
+4 -4
View File
@@ -55,7 +55,7 @@ pub fn clean_rust_type_syntax(input: String) -> String {
} }
} }
'<' => { '<' => {
while let Some(' ') = result.chars().rev().next() { while let Some(' ') = result.chars().next_back() {
result.pop(); result.pop();
} }
result.push('<'); result.push('<');
@@ -64,7 +64,7 @@ pub fn clean_rust_type_syntax(input: String) -> String {
} }
} }
'>' => { '>' => {
while let Some(' ') = result.chars().rev().next() { while let Some(' ') = result.chars().next_back() {
result.pop(); result.pop();
} }
result.push('>'); result.push('>');
@@ -74,7 +74,7 @@ pub fn clean_rust_type_syntax(input: String) -> String {
} }
'-' => { '-' => {
if let Some('>') = chars.peek() { if let Some('>') = chars.peek() {
while let Some(' ') = result.chars().rev().next() { while let Some(' ') = result.chars().next_back() {
result.pop(); result.pop();
} }
result.push_str(" -> "); result.push_str(" -> ");
@@ -88,7 +88,7 @@ pub fn clean_rust_type_syntax(input: String) -> String {
} }
':' => { ':' => {
if let Some(':') = chars.peek() { if let Some(':') = chars.peek() {
while let Some(' ') = result.chars().rev().next() { while let Some(' ') = result.chars().next_back() {
result.pop(); result.pop();
} }
} }
+4 -6
View File
@@ -47,22 +47,20 @@ pub fn generate_hierarchical_tree(input: TokenStream) -> syn::Result<TokenStream
}) })
} else { } else {
let error_msg = match fields.unnamed.len() { let error_msg = match fields.unnamed.len() {
0 => format!("Remove the unnecessary `()` from the `{}` message enum variant.", variant_type), 0 => format!("Remove the unnecessary `()` from the `{variant_type}` message enum variant."),
1 => { 1 => {
let field_type = &fields.unnamed.first().unwrap().ty; let field_type = &fields.unnamed.first().unwrap().ty;
format!( format!(
"The `{}` message should be defined as a struct-style (not tuple-style) enum variant to maintain consistent formatting across all editor messages.\n\ "The `{variant_type}` message should be defined as a struct-style (not tuple-style) enum variant to maintain consistent formatting across all editor messages.\n\
Replace `{}` with a named field using {{curly braces}} instead of a positional field using (parentheses).", Replace `{}` with a named field using {{curly braces}} instead of a positional field using (parentheses).",
variant_type,
field_type.to_token_stream() field_type.to_token_stream()
) )
} }
_ => { _ => {
let field_types = fields.unnamed.iter().map(|f| f.ty.to_token_stream().to_string()).collect::<Vec<_>>().join(", "); let field_types = fields.unnamed.iter().map(|f| f.ty.to_token_stream().to_string()).collect::<Vec<_>>().join(", ");
format!( format!(
"The `{}` message should be defined as a struct-style (not tuple-style) enum variant to maintain consistent formatting across all editor messages.\n\ "The `{variant_type}` message should be defined as a struct-style (not tuple-style) enum variant to maintain consistent formatting across all editor messages.\n\
Replace `{}` with named fields using {{curly braces}} instead of positional fields using (parentheses).", Replace `{field_types}` with named fields using {{curly braces}} instead of positional fields using (parentheses)."
variant_type, field_types
) )
} }
}; };
@@ -89,7 +89,6 @@ Since Graphene is fundamentally a programming language, throughout this document
<!-- Our philosophy of building (bootstrapping) our own higher-level language features from the language itself --> <!-- Our philosophy of building (bootstrapping) our own higher-level language features from the language itself -->
<!-- Call arguments, construction arguments, `.eval()`, recompiling when construction argument values are updated but not when call argument data changes --> <!-- Call arguments, construction arguments, `.eval()`, recompiling when construction argument values are updated but not when call argument data changes -->
<!-- Compose nodes and automatic/manual composition -->
<!-- Extract/inject nodes and metaprogramming --> <!-- Extract/inject nodes and metaprogramming -->
<!-- Cache nodes and stable node IDs --> <!-- Cache nodes and stable node IDs -->
<!-- Graph rewriting step (currently used only to remove Identity nodes), <!-- Graph rewriting step (currently used only to remove Identity nodes),