mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-09 03:28:11 +08:00
Merge branch 'master' into operation-tool
This commit is contained in:
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
+45
-6
@@ -2,7 +2,7 @@ use crate::CustomEvent;
|
||||
use crate::cef::WindowSize;
|
||||
use crate::consts::{APP_NAME, CEF_MESSAGE_LOOP_MAX_ITERATIONS};
|
||||
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 rfd::AsyncFileDialog;
|
||||
@@ -35,6 +35,8 @@ pub(crate) struct WinitApp {
|
||||
last_ui_update: Instant,
|
||||
avg_frame_time: f32,
|
||||
start_render_sender: SyncSender<()>,
|
||||
web_communication_initialized: bool,
|
||||
web_communication_startup_buffer: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl WinitApp {
|
||||
@@ -61,6 +63,8 @@ impl WinitApp {
|
||||
last_ui_update: Instant::now(),
|
||||
avg_frame_time: 0.,
|
||||
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");
|
||||
return;
|
||||
};
|
||||
self.cef_context.send_web_message(bytes);
|
||||
self.send_or_queue_web_message(bytes);
|
||||
}
|
||||
DesktopFrontendMessage::OpenFileDialog { title, filters, context } => {
|
||||
let event_loop_proxy = self.event_loop_proxy.clone();
|
||||
@@ -148,6 +152,15 @@ impl WinitApp {
|
||||
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);
|
||||
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 {
|
||||
@@ -211,10 +232,24 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||
tracing::info!("Winit window created and ready");
|
||||
|
||||
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 {
|
||||
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::NodeGraphExecutionResult(result) => match result {
|
||||
NodeGraphExecutionResult::HasRun(texture) => {
|
||||
@@ -250,6 +285,12 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||
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 {
|
||||
WindowEvent::CloseRequested => {
|
||||
tracing::info!("The close button was pressed; stopping");
|
||||
event_loop.exit();
|
||||
let _ = self.event_loop_proxy.send_event(CustomEvent::CloseWindow);
|
||||
}
|
||||
WindowEvent::Resized(PhysicalSize { width, height }) => {
|
||||
let _ = self.window_size_sender.send(WindowSize::new(width as usize, height as usize));
|
||||
self.cef_context.notify_of_resize();
|
||||
}
|
||||
|
||||
WindowEvent::RedrawRequested => {
|
||||
let Some(ref mut graphics_state) = self.graphics_state else { return };
|
||||
// Only rerender once we have a new ui texture to display
|
||||
|
||||
@@ -46,6 +46,7 @@ pub(crate) trait CefEventHandler: Clone {
|
||||
/// 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.
|
||||
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
|
||||
fn initialized_web_communication(&self);
|
||||
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));
|
||||
}
|
||||
|
||||
fn initialized_web_communication(&self) {
|
||||
let _ = self.event_loop_proxy.send_event(CustomEvent::WebCommunicationInitialized);
|
||||
}
|
||||
|
||||
fn receive_web_message(&self, message: &[u8]) {
|
||||
let Some(desktop_wrapper_message) = deserialize_editor_message(message) else {
|
||||
tracing::error!("Failed to deserialize web message");
|
||||
|
||||
@@ -32,6 +32,10 @@ impl<H: CefEventHandler> ImplClient for BrowserProcessClientImpl<H> {
|
||||
) -> ::std::os::raw::c_int {
|
||||
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
|
||||
match unpacked_message {
|
||||
Some(UnpackedMessage {
|
||||
message_type: MessageType::Initialized,
|
||||
data: _,
|
||||
}) => self.event_handler.initialized_web_communication(),
|
||||
Some(UnpackedMessage {
|
||||
message_type: MessageType::SendToNative,
|
||||
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>) {
|
||||
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 {
|
||||
tracing::error!("V8 context is not available");
|
||||
return;
|
||||
};
|
||||
|
||||
let mut v8_handler = V8Handler::new(BrowserProcessV8HandlerImpl::new());
|
||||
let Some(mut function) = v8_value_create_function(Some(&CefString::from(function_name)), Some(&mut v8_handler)) else {
|
||||
tracing::error!("Failed to create V8 function {function_name}");
|
||||
return;
|
||||
};
|
||||
let initialized_function_name = "initializeNativeCommunication";
|
||||
let send_function_name = "sendNativeMessage";
|
||||
|
||||
let Some(global) = context.global() else {
|
||||
tracing::error!("Global object is not available in V8 context");
|
||||
return;
|
||||
};
|
||||
global.set_value_bykey(Some(&CefString::from(function_name)), Some(&mut function), V8Propertyattribute::default());
|
||||
register_js_function(context, initialized_function_name);
|
||||
register_js_function(context, send_function_name);
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_render_process_handler_t {
|
||||
|
||||
@@ -21,8 +21,11 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl {
|
||||
_retval: Option<&mut Option<V8Value>>,
|
||||
_exception: Option<&mut cef::CefString>,
|
||||
) -> ::std::os::raw::c_int {
|
||||
if let Some(name) = name {
|
||||
if name.to_string() == "sendNativeMessage" {
|
||||
match name.map(|s| s.to_string()).unwrap_or_default().as_str() {
|
||||
"initializeNativeCommunication" => {
|
||||
v8_context_get_current_context().send_message(MessageType::Initialized, vec![0u8].as_slice());
|
||||
}
|
||||
"sendNativeMessage" => {
|
||||
let Some(args) = arguments else {
|
||||
tracing::error!("No arguments provided to sendNativeMessage");
|
||||
return 0;
|
||||
@@ -48,6 +51,9 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl {
|
||||
|
||||
return 1;
|
||||
}
|
||||
name => {
|
||||
tracing::error!("Unknown V8 function called: {}", name);
|
||||
}
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
use cef::{CefString, Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};
|
||||
|
||||
pub(crate) enum MessageType {
|
||||
Initialized,
|
||||
SendToJS,
|
||||
SendToNative,
|
||||
}
|
||||
impl From<MessageType> for MessageInfo {
|
||||
fn from(val: MessageType) -> Self {
|
||||
match val {
|
||||
MessageType::Initialized => MessageInfo {
|
||||
name: "initialized".to_string(),
|
||||
target: cef_process_id_t::PID_BROWSER.into(),
|
||||
},
|
||||
MessageType::SendToJS => MessageInfo {
|
||||
name: "send_to_js".to_string(),
|
||||
target: cef_process_id_t::PID_RENDERER.into(),
|
||||
@@ -22,6 +27,7 @@ impl TryFrom<String> for MessageType {
|
||||
type Error = ();
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match value.as_str() {
|
||||
"initialized" => Ok(MessageType::Initialized),
|
||||
"send_to_js" => Ok(MessageType::SendToJS),
|
||||
"send_to_native" => Ok(MessageType::SendToNative),
|
||||
_ => Err(()),
|
||||
|
||||
@@ -87,6 +87,7 @@ impl<'a> GraphiteFrontendResourceHandler<'a> {
|
||||
match ext {
|
||||
"html" => Some("text/html".to_string()),
|
||||
"css" => Some("text/css".to_string()),
|
||||
"txt" => Some("text/plain".to_string()),
|
||||
"wasm" => Some("application/wasm".to_string()),
|
||||
"js" => Some("application/javascript".to_string()),
|
||||
"png" => Some("image/png".to_string()),
|
||||
|
||||
@@ -21,8 +21,10 @@ use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext};
|
||||
pub(crate) enum CustomEvent {
|
||||
UiUpdate(wgpu::Texture),
|
||||
ScheduleBrowserWork(Instant),
|
||||
WebCommunicationInitialized,
|
||||
DesktopWrapperMessage(DesktopWrapperMessage),
|
||||
NodeGraphExecutionResult(NodeGraphExecutionResult),
|
||||
CloseWindow,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use graphene_std::Color;
|
||||
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::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());
|
||||
}
|
||||
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 } => {
|
||||
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),
|
||||
}
|
||||
None
|
||||
|
||||
@@ -30,6 +30,11 @@ pub enum DesktopFrontendMessage {
|
||||
height: f32,
|
||||
},
|
||||
UpdateOverlays(vello::Scene),
|
||||
UpdateWindowState {
|
||||
maximized: bool,
|
||||
minimized: bool,
|
||||
},
|
||||
CloseWindow,
|
||||
}
|
||||
|
||||
pub struct FileFilter {
|
||||
@@ -47,6 +52,7 @@ pub enum DesktopWrapperMessage {
|
||||
ImportSvg { path: PathBuf, content: Vec<u8> },
|
||||
ImportImage { path: PathBuf, content: Vec<u8> },
|
||||
PollNodeGraphEvaluation,
|
||||
UpdatePlatform(Platform),
|
||||
}
|
||||
|
||||
pub enum OpenFileDialogContext {
|
||||
@@ -58,3 +64,9 @@ pub enum SaveFileDialogContext {
|
||||
Document { document_id: DocumentId, content: Vec<u8> },
|
||||
File { content: Vec<u8> },
|
||||
}
|
||||
|
||||
pub enum Platform {
|
||||
Windows,
|
||||
Mac,
|
||||
Linux,
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ mod test {
|
||||
assert_eq!(layers_before_copy.len(), 3);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use super::app_window_message_handler::AppWindowPlatform;
|
||||
|
||||
#[impl_message(Message, AppWindow)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum AppWindowMessage {
|
||||
AppWindowMinimize,
|
||||
AppWindowMaximize,
|
||||
AppWindowUpdatePlatform { platform: AppWindowPlatform },
|
||||
AppWindowClose,
|
||||
}
|
||||
|
||||
@@ -6,34 +6,34 @@ use graphite_proc_macros::{ExtractField, message_handler_data};
|
||||
pub struct AppWindowMessageHandler {
|
||||
platform: AppWindowPlatform,
|
||||
maximized: bool,
|
||||
viewport_hole_punch_active: bool,
|
||||
minimized: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
|
||||
fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<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 => {
|
||||
self.maximized = !self.maximized;
|
||||
responses.add(FrontendMessage::UpdateMaximized { maximized: self.maximized });
|
||||
|
||||
self.viewport_hole_punch_active = !self.viewport_hole_punch_active;
|
||||
responses.add(FrontendMessage::UpdateViewportHolePunch {
|
||||
active: self.viewport_hole_punch_active,
|
||||
responses.add(FrontendMessage::UpdateWindowState {
|
||||
maximized: self.maximized,
|
||||
minimized: self.minimized,
|
||||
});
|
||||
}
|
||||
AppWindowMessage::AppWindowClose => {
|
||||
self.platform = AppWindowPlatform::Web;
|
||||
AppWindowMessage::AppWindowMinimize => {
|
||||
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 });
|
||||
}
|
||||
AppWindowMessage::AppWindowClose => {
|
||||
responses.add(FrontendMessage::CloseWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -329,9 +329,11 @@ pub enum FrontendMessage {
|
||||
UpdatePlatform {
|
||||
platform: AppWindowPlatform,
|
||||
},
|
||||
UpdateMaximized {
|
||||
UpdateWindowState {
|
||||
maximized: bool,
|
||||
minimized: bool,
|
||||
},
|
||||
CloseWindow,
|
||||
UpdateViewportHolePunch {
|
||||
active: bool,
|
||||
},
|
||||
|
||||
@@ -479,7 +479,7 @@ impl<const LENGTH: usize> Iterator for BitVectorIter<'_, LENGTH> {
|
||||
impl<const LENGTH: usize> Display for BitVector<LENGTH> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for storage in self.0.iter().rev() {
|
||||
write!(f, "{:0width$b}", storage, width = STORAGE_SIZE_BITS)?;
|
||||
write!(f, "{storage:0STORAGE_SIZE_BITS$b}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -70,13 +70,11 @@ mod test {
|
||||
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
|
||||
// Print the current node
|
||||
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 {
|
||||
if is_last {
|
||||
("└── ", format!("{} ", prefix))
|
||||
} else {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
}
|
||||
("├── ", format!("{prefix}│ "))
|
||||
};
|
||||
|
||||
if tree.path().is_empty() {
|
||||
@@ -101,7 +99,7 @@ mod test {
|
||||
let is_last_field = i == len - 1;
|
||||
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() {
|
||||
let len = data.fields().len();
|
||||
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
("├── ", format!("{prefix}│ "))
|
||||
} else {
|
||||
("└── ", format!("{} ", prefix))
|
||||
("└── ", format!("{prefix} "))
|
||||
};
|
||||
|
||||
const FRONTEND_MESSAGE_STR: &str = "FrontendMessage";
|
||||
|
||||
@@ -1782,7 +1782,8 @@ impl DocumentMessageHandler {
|
||||
|
||||
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
|
||||
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
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
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) }
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
mod tests {
|
||||
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;
|
||||
};
|
||||
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()
|
||||
} else {
|
||||
None
|
||||
@@ -111,7 +111,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::None, true)],
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -151,19 +151,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -233,33 +233,33 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
// Secondary (left) input type coercion
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
// The monitor node is used to display a thumbnail in the UI
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(3), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||
..Default::default()
|
||||
@@ -349,7 +349,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
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
|
||||
DocumentNode {
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(TaggedValue), 1),
|
||||
@@ -365,7 +365,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
// The monitor node is used to display a thumbnail in the UI.
|
||||
@@ -373,12 +373,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![
|
||||
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
|
||||
NodeInput::node(NodeId(2), 0),
|
||||
@@ -495,13 +495,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
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),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
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),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -568,7 +568,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
..Default::default()
|
||||
@@ -630,20 +630,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
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),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -716,7 +716,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
category: "Raster: Pattern",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::None, false),
|
||||
@@ -783,7 +783,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
@@ -792,7 +792,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
@@ -801,7 +801,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
@@ -810,7 +810,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -888,13 +888,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -962,7 +962,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
|
||||
NodeInput::network(concrete!(BrushCache), 2),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER),
|
||||
..Default::default()
|
||||
}]
|
||||
@@ -1013,7 +1013,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
@@ -1032,7 +1032,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
@@ -1054,13 +1054,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||
..Default::default()
|
||||
@@ -1126,12 +1126,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNode {
|
||||
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),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||
..Default::default()
|
||||
@@ -1247,7 +1247,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1257,7 +1257,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1),
|
||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||
],
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1317,7 +1317,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![
|
||||
NodeInput::scope("editor-api"),
|
||||
NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false),
|
||||
@@ -1427,7 +1427,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1439,7 +1439,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(DVec2), 3),
|
||||
NodeInput::network(concrete!(DVec2), 4),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1524,25 +1524,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -1622,7 +1622,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
@@ -1637,25 +1637,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::node(NodeId(0), 0),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(3), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -1791,26 +1791,26 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(f64), 1),
|
||||
NodeInput::network(concrete!(u32), 2),
|
||||
],
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
call_argument: generic!(T),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -1944,10 +1944,10 @@ fn static_input_properties() -> InputProperties {
|
||||
"string".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
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 {
|
||||
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))
|
||||
}),
|
||||
|
||||
+2
-2
@@ -59,8 +59,8 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
inputs,
|
||||
manual_composition: Some(input_type.clone()),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()),
|
||||
call_argument: (input_type.clone()),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
|
||||
visible: true,
|
||||
skip_deduplication: false,
|
||||
..Default::default()
|
||||
|
||||
@@ -1516,7 +1516,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
let mut nodes = Vec::new();
|
||||
for node_id in &self.frontend_nodes {
|
||||
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
|
||||
log::error!("Could not get bbox for node: {:?}", node_id);
|
||||
log::error!("Could not get bbox for node: {node_id:?}");
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -1721,7 +1721,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
}
|
||||
NodeGraphMessage::ToggleLocked { node_id } => {
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -2486,7 +2486,7 @@ impl NodeGraphMessageHandler {
|
||||
data_type: frontend_data_type,
|
||||
name: "Output 1".to_string(),
|
||||
description: String::new(),
|
||||
resolved_type: format!("{:?}", output_type),
|
||||
resolved_type: format!("{output_type:?}"),
|
||||
connected_to,
|
||||
})
|
||||
} else {
|
||||
@@ -2518,7 +2518,7 @@ impl NodeGraphMessageHandler {
|
||||
data_type,
|
||||
name: output_name,
|
||||
description: String::new(),
|
||||
resolved_type: format!("{:?}", output_type),
|
||||
resolved_type: format!("{output_type:?}"),
|
||||
connected_to,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1156,7 +1156,7 @@ impl OverlayContextInternal {
|
||||
let move_to = last_point != Some(start_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
|
||||
|
||||
@@ -25,6 +25,7 @@ use kurbo::BezPath;
|
||||
use serde_json::{Value, json};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
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.
|
||||
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
@@ -73,10 +74,8 @@ impl NodeNetworkInterface {
|
||||
fix_network(network);
|
||||
}
|
||||
if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation {
|
||||
if protonode.name.contains("PathModifyNode") {
|
||||
if node.inputs.len() < 3 {
|
||||
node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath));
|
||||
}
|
||||
if protonode.name.contains("PathModifyNode") && node.inputs.len() < 3 {
|
||||
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
|
||||
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() {
|
||||
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) {
|
||||
*input = NodeInput::Node {
|
||||
node_id: new_id,
|
||||
output_index,
|
||||
lambda,
|
||||
};
|
||||
*input = NodeInput::Node { node_id: new_id, output_index };
|
||||
} else {
|
||||
// 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);
|
||||
@@ -547,12 +542,11 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
}
|
||||
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.
|
||||
let manual_composition_offset = if node.manual_composition.is_some() { 1 } else { 0 };
|
||||
// Offset the input index by 1 since the proto node also includes the type of the input passed as a call argument.
|
||||
self.resolved_types
|
||||
.types
|
||||
.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))
|
||||
}
|
||||
DocumentNodeImplementation::Extract => None,
|
||||
@@ -581,7 +575,7 @@ impl NodeNetworkInterface {
|
||||
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 {
|
||||
log::error!("Could not get type");
|
||||
@@ -821,7 +815,7 @@ impl NodeNetworkInterface {
|
||||
data_type,
|
||||
name,
|
||||
description,
|
||||
resolved_type: format!("{:?}", input_type),
|
||||
resolved_type: format!("{input_type:?}"),
|
||||
connected_to,
|
||||
},
|
||||
click_target,
|
||||
@@ -1069,7 +1063,7 @@ impl NodeNetworkInterface {
|
||||
|
||||
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 {
|
||||
log::error!("Could not get reference for node: {:?}", node_id);
|
||||
log::error!("Could not get reference for node: {node_id:?}");
|
||||
return None;
|
||||
};
|
||||
Some(&node_metadata.persistent_metadata.reference)
|
||||
@@ -1287,7 +1281,7 @@ impl NodeNetworkInterface {
|
||||
let artboard = self.document_node(&artboard_node_identifier.to_node(), &[]);
|
||||
let clip_input = artboard.unwrap().inputs.get(5).unwrap();
|
||||
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(
|
||||
self.document_metadata.bounding_box_document(layer).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();
|
||||
|
||||
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.skip_deduplication = old_node.skip_deduplication;
|
||||
node.original_location = old_node.original_location;
|
||||
@@ -2522,7 +2516,7 @@ impl NodeNetworkInterface {
|
||||
InputConnector::Node { node_id, input_index } => {
|
||||
let input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?;
|
||||
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;
|
||||
};
|
||||
wire.clone()
|
||||
@@ -2530,7 +2524,7 @@ impl NodeNetworkInterface {
|
||||
InputConnector::Export(export_index) => {
|
||||
let network_metadata = self.network_metadata(network_path)?;
|
||||
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;
|
||||
};
|
||||
wire.clone()
|
||||
@@ -2701,12 +2695,12 @@ impl NodeNetworkInterface {
|
||||
return None;
|
||||
}
|
||||
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;
|
||||
};
|
||||
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 {
|
||||
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;
|
||||
};
|
||||
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.
|
||||
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 {
|
||||
log::error!("Could not get dom rect for wire end: {:?}", input);
|
||||
log::error!("Could not get dom rect for wire end: {input:?}");
|
||||
return None;
|
||||
};
|
||||
// An upstream output could not be found, so the wire does not exist, but it should still be loaded as as empty vector
|
||||
@@ -2741,7 +2735,7 @@ impl NodeNetworkInterface {
|
||||
return Some((BezPath::new(), false));
|
||||
};
|
||||
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;
|
||||
};
|
||||
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()
|
||||
.0
|
||||
.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)))
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
@@ -3366,7 +3360,7 @@ impl NodeNetworkInterface {
|
||||
self.selected_nodes()
|
||||
.0
|
||||
.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)))
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
@@ -4138,7 +4132,7 @@ impl NodeNetworkInterface {
|
||||
if let DocumentNodeImplementation::Network(network) = &node.implementation {
|
||||
let number_of_exports = network.exports.len();
|
||||
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;
|
||||
};
|
||||
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
|
||||
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 {
|
||||
log::error!("Could not get nested network in set_implementation");
|
||||
return;
|
||||
@@ -4164,7 +4158,7 @@ impl NodeNetworkInterface {
|
||||
log::error!("Could not get node in set_implementation");
|
||||
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]) {
|
||||
|
||||
@@ -20,6 +20,7 @@ use std::collections::HashMap;
|
||||
const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
|
||||
("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePolylineNode"),
|
||||
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
|
||||
("\"manual_composition\":null", "\"manual_composition\":{\"Generic\":\"T\"}"),
|
||||
];
|
||||
|
||||
pub struct NodeReplacement<'a> {
|
||||
@@ -551,7 +552,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
||||
let mut default_template = NodeTemplate::default();
|
||||
default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone());
|
||||
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
|
||||
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) {
|
||||
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` as their call argument
|
||||
if node.call_argument == graph_craft::concrete!(()) || node.call_argument == graph_craft::concrete!(graphene_std::transform::Footprint) {
|
||||
document
|
||||
.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
|
||||
@@ -1083,8 +1084,8 @@ mod tests {
|
||||
*hashmap.entry(node.node.clone()).or_default() += 1;
|
||||
});
|
||||
let duplicates = hashmap.iter().filter(|(_, count)| **count > 1).map(|(node, _)| &node.name).collect::<Vec<_>>();
|
||||
if duplicates.len() > 0 {
|
||||
panic!("Duplicate entries in `NODE_REPLACEMENTS`: {:?}", duplicates);
|
||||
if !duplicates.is_empty() {
|
||||
panic!("Duplicate entries in `NODE_REPLACEMENTS`: {duplicates:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,14 +101,9 @@ impl Circle {
|
||||
};
|
||||
|
||||
let dimensions = (start - end).abs();
|
||||
let radius: f64;
|
||||
|
||||
// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly
|
||||
if dimensions.x > dimensions.y {
|
||||
radius = dimensions.y / 2.;
|
||||
} else {
|
||||
radius = dimensions.x / 2.;
|
||||
}
|
||||
let radius: f64 = if dimensions.x > dimensions.y { dimensions.y / 2. } else { dimensions.x / 2. };
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
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)> {
|
||||
let document = editor.active_document();
|
||||
let network_interface = &document.network_interface;
|
||||
let node_id = network_interface
|
||||
|
||||
network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(network_interface)
|
||||
.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 {
|
||||
return None;
|
||||
};
|
||||
Some((start, end))
|
||||
})
|
||||
.next();
|
||||
node_id
|
||||
.next()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -245,11 +245,7 @@ mod test_line_tool {
|
||||
editor.new_document().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::CanvasTiltSet {
|
||||
angle_radians: (30. as f64).to_radians(),
|
||||
})
|
||||
.await;
|
||||
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30_f64.to_radians() }).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 {
|
||||
let document = editor.active_document();
|
||||
@@ -261,15 +257,11 @@ mod test_line_tool {
|
||||
|
||||
assert!(
|
||||
(start_input - expected_start).length() < 1.,
|
||||
"Start point should match expected document coordinates. Got {:?}, expected {:?}",
|
||||
start_input,
|
||||
expected_start
|
||||
"Start point should match expected document coordinates. Got {start_input:?}, expected {expected_start:?}"
|
||||
);
|
||||
assert!(
|
||||
(end_input - expected_end).length() < 1.,
|
||||
"End point should match expected document coordinates. Got {:?}, expected {:?}",
|
||||
end_input,
|
||||
expected_end
|
||||
"End point should match expected document coordinates. Got {end_input:?}, expected {expected_end:?}"
|
||||
);
|
||||
} else {
|
||||
panic!("Line was not created successfully with transformed viewport");
|
||||
@@ -282,27 +274,19 @@ mod test_line_tool {
|
||||
editor.new_document().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 {
|
||||
match (start_input, end_input) {
|
||||
(start_input, end_input) => {
|
||||
let line_vec = end_input - start_input;
|
||||
let original_angle = line_vec.angle_to(DVec2::X);
|
||||
editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await;
|
||||
if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await {
|
||||
match (updated_start, updated_end) {
|
||||
(updated_start, updated_end) => {
|
||||
let updated_line_vec = updated_end - updated_start;
|
||||
let updated_angle = updated_line_vec.angle_to(DVec2::X);
|
||||
print!("{:?}", original_angle);
|
||||
print!("{:?}", updated_angle);
|
||||
assert!(
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let line_vec = end_input - start_input;
|
||||
let original_angle = line_vec.angle_to(DVec2::X);
|
||||
editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await;
|
||||
if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await {
|
||||
let updated_line_vec = updated_end - updated_start;
|
||||
let updated_angle = updated_line_vec.angle_to(DVec2::X);
|
||||
print!("{original_angle:?}");
|
||||
print!("{updated_angle:?}");
|
||||
assert!(
|
||||
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.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 {
|
||||
match (start_input, end_input) {
|
||||
(start_input, end_input) => {
|
||||
let expected_start = DVec2::new(0., 100.);
|
||||
let expected_end = DVec2::new(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)");
|
||||
}
|
||||
}
|
||||
let expected_start = DVec2::new(0., 100.);
|
||||
let expected_end = DVec2::new(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> {
|
||||
let instrumented = match editor.eval_graph().await {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {}", e),
|
||||
Err(e) => panic!("Failed to evaluate graph: {e}"),
|
||||
};
|
||||
instrumented
|
||||
.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> {
|
||||
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 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();
|
||||
|
||||
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() {
|
||||
@@ -434,7 +437,7 @@ mod test_freehand {
|
||||
for (i, (&expected, &actual)) in expected_captured_points.iter().zip(actual_positions.iter()).enumerate() {
|
||||
let distance = (expected - actual).length();
|
||||
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_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!(
|
||||
initial_segment_count,
|
||||
initial_point_count - 1,
|
||||
@@ -569,17 +572,13 @@ mod test_freehand {
|
||||
|
||||
assert!(
|
||||
extended_point_count > initial_point_count,
|
||||
"Expected more points after extension, initial: {}, after extension: {}",
|
||||
initial_point_count,
|
||||
extended_point_count
|
||||
"Expected more points after extension, initial: {initial_point_count}, after extension: {extended_point_count}"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
extended_segment_count,
|
||||
extended_point_count - 1,
|
||||
"Expected segments to be one less than points, points: {}, segments: {}",
|
||||
extended_point_count,
|
||||
extended_segment_count
|
||||
"Expected segments to be one less than points, points: {extended_point_count}, segments: {extended_segment_count}"
|
||||
);
|
||||
|
||||
let layer_count = {
|
||||
@@ -627,8 +626,8 @@ mod test_freehand {
|
||||
|
||||
let existing_layer_id = {
|
||||
let document = editor.active_document();
|
||||
let layer = document.metadata().all_layers().next().unwrap();
|
||||
layer
|
||||
|
||||
document.metadata().all_layers().next().unwrap()
|
||||
};
|
||||
|
||||
editor
|
||||
@@ -685,9 +684,7 @@ mod test_freehand {
|
||||
|
||||
assert!(
|
||||
final_point_count > initial_point_count,
|
||||
"Expected more points after appending to layer, initial: {}, after append: {}",
|
||||
initial_point_count,
|
||||
final_point_count
|
||||
"Expected more points after appending to layer, initial: {initial_point_count}, after append: {final_point_count}"
|
||||
);
|
||||
|
||||
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)> {
|
||||
let instrumented = match editor.eval_graph().await {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {}", e),
|
||||
Err(e) => panic!("Failed to evaluate graph: {e}"),
|
||||
};
|
||||
|
||||
let document = editor.active_document();
|
||||
@@ -573,7 +573,7 @@ mod test_gradient {
|
||||
let (fill, transform) = fills.first().unwrap();
|
||||
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) {
|
||||
@@ -586,7 +586,7 @@ mod test_gradient {
|
||||
);
|
||||
|
||||
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();
|
||||
assert!(
|
||||
positions.iter().any(|pos| (pos - 0.5).abs() < 0.1),
|
||||
"Expected to find a stop near position 0.5, but found: {:?}",
|
||||
positions
|
||||
"Expected to find a stop near position 0.5, but found: {positions:?}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -782,7 +781,7 @@ mod test_gradient {
|
||||
|
||||
// Verify the end point has been updated to the new position
|
||||
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]
|
||||
|
||||
@@ -202,11 +202,11 @@ impl SelectTool {
|
||||
let list = <BooleanOperation as graphene_std::choice_type::ChoiceTypeStatic>::list();
|
||||
list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
|
||||
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(doc);
|
||||
}
|
||||
IconButton::new(info.icon.as_deref().unwrap(), 24)
|
||||
IconButton::new(info.icon.unwrap(), 24)
|
||||
.tooltip(tooltip)
|
||||
.disabled(selected_count == 0)
|
||||
.on_update(move |_| {
|
||||
@@ -852,7 +852,13 @@ impl Fsm for SelectToolFsmState {
|
||||
if let Some(pivot) = pivot {
|
||||
let offset = tool_data
|
||||
.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();
|
||||
let shift = tool_data.pivot_gizmo_shift.unwrap_or_default();
|
||||
overlay_context.pivot(pivot + offset + shift, angle);
|
||||
@@ -895,7 +901,7 @@ impl Fsm for SelectToolFsmState {
|
||||
color
|
||||
} else {
|
||||
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;
|
||||
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;
|
||||
|
||||
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() {
|
||||
*v += offset;
|
||||
}
|
||||
@@ -1649,7 +1659,9 @@ impl Fsm for SelectToolFsmState {
|
||||
}
|
||||
(_, SelectToolMessage::PivotShift { offset, 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();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
return self;
|
||||
|
||||
@@ -606,11 +606,7 @@ mod test_spline_tool {
|
||||
|
||||
assert!(
|
||||
distance < epsilon,
|
||||
"Point {} position mismatch: expected {:?}, got {:?} (distance: {})",
|
||||
i,
|
||||
expected_point,
|
||||
actual_point,
|
||||
distance
|
||||
"Point {i} position mismatch: expected {expected_point:?}, got {actual_point:?} (distance: {distance})"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -644,8 +640,8 @@ mod test_spline_tool {
|
||||
// Verify initial spline has correct number of points and segments
|
||||
let initial_point_count = first_vector.point_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_segment_count, 2, "Expected 2 segments in initial spline, found {}", initial_segment_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}");
|
||||
|
||||
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_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_segment_count, 4, "Expected 4 segments in extended spline, found {}", extended_segment_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}");
|
||||
|
||||
// Verify the spline node is still the same
|
||||
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
|
||||
if let Err(e) = editor.eval_graph().await {
|
||||
panic!("Graph evaluation failed: {}", e);
|
||||
panic!("Graph evaluation failed: {e}");
|
||||
}
|
||||
|
||||
// Get the layer and vector data
|
||||
@@ -755,7 +751,7 @@ mod test_spline_tool {
|
||||
|
||||
// Evaluating the graph to ensure everything is processed
|
||||
if let Err(e) = editor.eval_graph().await {
|
||||
panic!("Graph evaluation failed: {}", e);
|
||||
panic!("Graph evaluation failed: {e}");
|
||||
}
|
||||
|
||||
// Get the layer and vector data
|
||||
@@ -793,7 +789,7 @@ mod test_spline_tool {
|
||||
|
||||
// Evaluating the graph to ensure everything is processed
|
||||
if let Err(e) = editor.eval_graph().await {
|
||||
panic!("Graph evaluation failed: {}", e);
|
||||
panic!("Graph evaluation failed: {e}");
|
||||
}
|
||||
|
||||
// Get the layer and vector data
|
||||
@@ -832,7 +828,7 @@ mod test_spline_tool {
|
||||
|
||||
editor.handle_message(SplineToolMessage::Confirm).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
|
||||
@@ -889,8 +885,8 @@ mod test_spline_tool {
|
||||
let point_count = vector.point_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!(segment_count, 2, "Expected 2 segments in the spline, found {}", segment_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}");
|
||||
|
||||
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| {
|
||||
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 {
|
||||
self.typing.string.clone()
|
||||
}
|
||||
@@ -892,7 +892,7 @@ mod test_transform_layer {
|
||||
let final_transform = get_layer_transform(&mut editor, layer).await.unwrap();
|
||||
|
||||
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]
|
||||
@@ -927,9 +927,7 @@ mod test_transform_layer {
|
||||
// Verify transform is either restored to original OR reset to identity
|
||||
assert!(
|
||||
(final_translation - original_translation).length() < 5. || final_translation.length() < 0.001,
|
||||
"Transform neither restored to original nor reset to identity. Original: {:?}, Final: {:?}",
|
||||
original_translation,
|
||||
final_translation
|
||||
"Transform neither restored to original nor reset to identity. Original: {original_translation:?}, Final: {final_translation:?}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -958,11 +956,11 @@ mod test_transform_layer {
|
||||
editor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;
|
||||
|
||||
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)
|
||||
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]
|
||||
@@ -984,7 +982,7 @@ mod test_transform_layer {
|
||||
assert!(!after_cancel.translation.y.is_nan(), "Transform is NaN after cancel");
|
||||
|
||||
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]
|
||||
@@ -1019,9 +1017,7 @@ mod test_transform_layer {
|
||||
|
||||
assert!(
|
||||
scale_diff_x > 0.1 || scale_diff_y > 0.1,
|
||||
"Scaling should have changed the transform matrix. Diffs: x={}, y={}",
|
||||
scale_diff_x,
|
||||
scale_diff_y
|
||||
"Scaling should have changed the transform matrix. Diffs: x={scale_diff_x}, y={scale_diff_y}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1050,7 +1046,7 @@ mod test_transform_layer {
|
||||
|
||||
// Also check translation component is similar
|
||||
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]
|
||||
@@ -1077,9 +1073,7 @@ mod test_transform_layer {
|
||||
let actual_translation = after_grab_transform.translation - original_transform.translation;
|
||||
assert!(
|
||||
(actual_translation - expected_translation).length() < 1e-5,
|
||||
"Expected translation of {:?}, got {:?}",
|
||||
expected_translation,
|
||||
actual_translation
|
||||
"Expected translation of {expected_translation:?}, got {actual_translation:?}"
|
||||
);
|
||||
|
||||
// 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();
|
||||
assert!(
|
||||
after_scale_det >= 2. * before_scale_det,
|
||||
"Scale should increase the determinant of the matrix (before: {}, after: {})",
|
||||
before_scale_det,
|
||||
after_scale_det
|
||||
"Scale should increase the determinant of the matrix (before: {before_scale_det}, after: {after_scale_det})"
|
||||
);
|
||||
|
||||
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_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_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {}", scale_y);
|
||||
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}");
|
||||
}
|
||||
|
||||
#[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_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_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {}", scale_y);
|
||||
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}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1191,11 +1183,7 @@ mod test_transform_layer {
|
||||
|
||||
// Rotate the document view (45 degrees)
|
||||
editor.handle_message(NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }).await;
|
||||
editor
|
||||
.handle_message(NavigationMessage::CanvasTiltSet {
|
||||
angle_radians: (45. as f64).to_radians(),
|
||||
})
|
||||
.await;
|
||||
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45_f64.to_radians() }).await;
|
||||
editor.handle_message(TransformLayerMessage::BeginRotate).await;
|
||||
|
||||
editor.handle_message(TransformLayerMessage::TypeDigit { digit: 9 }).await;
|
||||
@@ -1210,7 +1198,7 @@ mod test_transform_layer {
|
||||
|
||||
// Normalize angle between 0 and 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]
|
||||
@@ -1265,8 +1253,8 @@ mod test_transform_layer {
|
||||
// Verify scale is near zero.
|
||||
let scale_x = near_zero_transform.matrix2.x_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_y < 0.001, "Scale factor Y should be near zero, got: {}", scale_y);
|
||||
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_x > 0., "Scale factor X should not be exactly zero");
|
||||
assert!(scale_y > 0., "Scale factor Y should not be exactly zero");
|
||||
|
||||
|
||||
@@ -431,7 +431,7 @@ mod test {
|
||||
let monitor_node = DocumentNode {
|
||||
inputs: vec![input],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(graph_craft::generic!(T)),
|
||||
call_argument: graph_craft::generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -432,7 +432,7 @@ pub struct InspectResult {
|
||||
|
||||
impl InspectResult {
|
||||
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 {
|
||||
inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(graph_craft::generic!(T)),
|
||||
call_argument: graph_craft::generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -356,10 +356,13 @@ export class UpdatePlatform extends JsMessage {
|
||||
readonly platform!: AppWindowPlatform;
|
||||
}
|
||||
|
||||
export class UpdateMaximized extends JsMessage {
|
||||
export class UpdateWindowState extends JsMessage {
|
||||
readonly maximized!: boolean;
|
||||
readonly minimized!: boolean;
|
||||
}
|
||||
|
||||
export class CloseWindow extends JsMessage {}
|
||||
|
||||
export class UpdateViewportHolePunch extends JsMessage {
|
||||
readonly active!: boolean;
|
||||
}
|
||||
@@ -1741,7 +1744,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||
UpdateLayersPanelControlBarLeftLayout,
|
||||
UpdateLayersPanelControlBarRightLayout,
|
||||
UpdateLayerWidths,
|
||||
UpdateMaximized,
|
||||
UpdateWindowState,
|
||||
UpdateMenuBarLayout,
|
||||
UpdateMouseCursor,
|
||||
UpdateNodeGraphControlBarLayout,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
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
|
||||
export function createAppWindowState(editor: Editor) {
|
||||
@@ -20,9 +20,9 @@ export function createAppWindowState(editor: Editor) {
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateMaximized, (maximized) => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdateWindowState, (updateWindowState) => {
|
||||
update((state) => {
|
||||
state.maximized = maximized.maximized;
|
||||
state.maximized = updateWindowState.maximized;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -242,6 +242,9 @@ impl EditorHandle {
|
||||
|
||||
#[wasm_bindgen(js_name = initAfterFrontendReady)]
|
||||
pub fn init_after_frontend_ready(&self, platform: String) {
|
||||
#[cfg(feature = "native")]
|
||||
crate::native_communcation::initialize_native_communication();
|
||||
|
||||
// Send initialization messages
|
||||
let platform = match platform.as_str() {
|
||||
"Windows" => Platform::Windows,
|
||||
|
||||
@@ -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) {
|
||||
let global = js_sys::global();
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ pub fn system_desc_derive(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
|
||||
fn replace_lifetimes(generics: &syn::Generics, replacement: &str) -> Vec<proc_macro2::TokenStream> {
|
||||
let params = generics
|
||||
generics
|
||||
.params
|
||||
.iter()
|
||||
.map(|param| {
|
||||
@@ -72,6 +72,5 @@ fn replace_lifetimes(generics: &syn::Generics, replacement: &str) -> Vec<proc_ma
|
||||
};
|
||||
quote! {#param}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
params
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
@@ -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, scale) = parse_unit(unit_pairs)?;
|
||||
|
||||
println!("found unit: {:?}", unit);
|
||||
println!("found unit: {unit:?}");
|
||||
|
||||
Ok((
|
||||
match literal {
|
||||
|
||||
@@ -45,7 +45,7 @@ pub fn commands_from_path_data(d: &str) -> Result<Vec<PathCommand>, BooleanError
|
||||
*i += cap[0].len();
|
||||
&cap[1] == "1"
|
||||
} 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)
|
||||
.map(|cmd| match cmd {
|
||||
PathCommand::Absolute(abs_cmd) => match abs_cmd {
|
||||
AbsolutePathCommand::H(dx) => format!("H {:.12}", dx),
|
||||
AbsolutePathCommand::V(dy) => format!("V {:.12}", dy),
|
||||
AbsolutePathCommand::H(dx) => format!("H {dx:.12}"),
|
||||
AbsolutePathCommand::V(dy) => format!("V {dy:.12}"),
|
||||
AbsolutePathCommand::M(p) => format!("M {:.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),
|
||||
@@ -146,18 +146,18 @@ pub fn path_to_path_data(path: &Path, eps: f64) -> String {
|
||||
AbsolutePathCommand::Z => "Z".to_string(),
|
||||
},
|
||||
PathCommand::Relative(rel_cmd) => match rel_cmd {
|
||||
RelativePathCommand::M(dx, dy) => format!("m {:.12},{:.12}", dx, dy),
|
||||
RelativePathCommand::L(dx, dy) => format!("l {:.12},{:.12}", dx, dy),
|
||||
RelativePathCommand::H(dx) => format!("h {:.12}", dx),
|
||||
RelativePathCommand::V(dy) => format!("v {:.12}", dy),
|
||||
RelativePathCommand::C(dx1, dy1, dx2, dy2, dx, dy) => format!("c{:.12},{:.12} {:.12},{:.12} {:.12},{:.12}", dx1, dy1, dx2, dy2, dx, dy),
|
||||
RelativePathCommand::M(dx, dy) => format!("m {dx:.12},{dy:.12}"),
|
||||
RelativePathCommand::L(dx, dy) => format!("l {dx:.12},{dy:.12}"),
|
||||
RelativePathCommand::H(dx) => format!("h {dx:.12}"),
|
||||
RelativePathCommand::V(dy) => format!("v {dy:.12}"),
|
||||
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) => {
|
||||
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) => {
|
||||
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) => {
|
||||
format!("a {:.12} {:.12} {:.12} {} {} {:.12},{:.12}", rx, ry, x_axis_rotation, large_arc_flag as u8, sweep_flag as u8, dx, dy)
|
||||
}
|
||||
|
||||
@@ -551,13 +551,15 @@ fn round_point(point: DVec2) -> I64Vec2 {
|
||||
(point * ROUNDING_FACTOR).round().as_i64vec2()
|
||||
}
|
||||
|
||||
type Edges = SmallVec<[(PathSegment, u8, MajorEdgeKey, MajorEdgeKey); 2]>;
|
||||
|
||||
fn find_vertices(edges: &[MajorGraphEdgeStage1]) -> MajorGraph {
|
||||
let mut graph = MajorGraph {
|
||||
edges: SlotMap::with_capacity_and_key(edges.len() * 2),
|
||||
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);
|
||||
|
||||
for (seg, parent) in edges {
|
||||
|
||||
@@ -11,7 +11,7 @@ The graph that is presented to users in the editor is known as the document grap
|
||||
```rs
|
||||
pub struct DocumentNode {
|
||||
pub inputs: Vec<NodeInput>,
|
||||
pub manual_composition: Option<Type>,
|
||||
pub call_argument: Type,
|
||||
pub implementation: DocumentNodeImplementation,
|
||||
pub skip_deduplication: bool,
|
||||
pub visible: bool,
|
||||
|
||||
@@ -249,7 +249,7 @@ struct Logger;
|
||||
|
||||
impl NodeGraphUpdateSender for Logger {
|
||||
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:?}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod color;
|
||||
mod color_traits;
|
||||
mod color_types;
|
||||
mod discrete_srgb;
|
||||
|
||||
pub use color::*;
|
||||
pub use color_traits::*;
|
||||
pub use color_types::*;
|
||||
pub use discrete_srgb::*;
|
||||
|
||||
@@ -21,7 +21,6 @@ pub mod raster;
|
||||
pub mod raster_types;
|
||||
pub mod registry;
|
||||
pub mod render_complexity;
|
||||
pub mod structural;
|
||||
pub mod subpath;
|
||||
pub mod table;
|
||||
pub mod text;
|
||||
|
||||
@@ -287,6 +287,6 @@ mod test {
|
||||
fn display() {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,21 @@ use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// 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)]
|
||||
pub struct MemoNode<T, CachedNode> {
|
||||
cache: Arc<Mutex<Option<(u64, T)>>>,
|
||||
@@ -50,6 +65,7 @@ impl<T, CachedNode> MemoNode<T, CachedNode> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod memo {
|
||||
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");
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct MemoHash<T: Hash> {
|
||||
hash: u64,
|
||||
value: T,
|
||||
value: Arc<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> {
|
||||
pub fn new(value: T) -> Self {
|
||||
let hash = Self::calc_hash(&value);
|
||||
Self { hash, value }
|
||||
Self { hash, value: value.into() }
|
||||
}
|
||||
pub fn new_with_hash(value: T, hash: u64) -> Self {
|
||||
Self { hash, value }
|
||||
Self { hash, value: value.into() }
|
||||
}
|
||||
|
||||
fn calc_hash(data: &T) -> u64 {
|
||||
@@ -197,7 +213,7 @@ impl<T: Hash> MemoHash<T> {
|
||||
pub fn inner_mut(&mut self) -> MemoHashGuard<'_, T> {
|
||||
MemoHashGuard { inner: self }
|
||||
}
|
||||
pub fn into_inner(self) -> T {
|
||||
pub fn into_inner(self) -> Arc<T> {
|
||||
self.value
|
||||
}
|
||||
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 {
|
||||
&mut self.inner.value
|
||||
Arc::make_mut(&mut self.inner.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use graphene_core_shaders::Ctx;
|
||||
|
||||
use crate::Node;
|
||||
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> {}
|
||||
|
||||
// Into
|
||||
pub struct IntoNode<O>(PhantomData<O>);
|
||||
impl<O> IntoNode<O> {
|
||||
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() })
|
||||
}
|
||||
#[node_macro::node(skip_impl)]
|
||||
fn into<'i, T: 'i + Send + Into<O>, O: 'i + Send>(_: impl Ctx, value: T, _out_ty: PhantomData<O>) -> O {
|
||||
value.into()
|
||||
}
|
||||
|
||||
/// The [`Convert`] trait allows for conversion between Rust primitive numeric types.
|
||||
@@ -122,25 +105,9 @@ impl_convert!(u128);
|
||||
impl_convert!(isize);
|
||||
impl_convert!(usize);
|
||||
|
||||
// Convert
|
||||
pub struct ConvertNode<O>(PhantomData<O>);
|
||||
impl<_O> ConvertNode<_O> {
|
||||
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() })
|
||||
}
|
||||
#[node_macro::node(skip_impl)]
|
||||
fn convert<'i, T: 'i + Send + Convert<O>, O: 'i + Send>(_: impl Ctx, value: T, _out_ty: PhantomData<O>) -> O {
|
||||
value.convert()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -258,7 +258,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
|
||||
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();
|
||||
println!("{}", serialized);
|
||||
println!("{serialized}");
|
||||
let deserialized: Image<Color> = serde_json::from_str(&serialized).unwrap();
|
||||
println!("{:?}", deserialized);
|
||||
println!("{deserialized:?}");
|
||||
|
||||
assert_eq!(image, deserialized);
|
||||
}
|
||||
|
||||
@@ -153,13 +153,14 @@ where
|
||||
{
|
||||
type Output = DynFuture<'input, O>;
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
fn eval(&'input self, input: I) -> Self::Output {
|
||||
{
|
||||
let node_name = self.node.node_name();
|
||||
let input = Box::new(input);
|
||||
let future = self.node.eval(input);
|
||||
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
|
||||
})
|
||||
}
|
||||
@@ -234,7 +235,7 @@ where
|
||||
};
|
||||
match dyn_any::downcast(input) {
|
||||
Ok(input) => Box::pin(output(*input)),
|
||||
Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, node_name),
|
||||
Err(e) => panic!("DynAnyNode Input, {e} in:\n{node_name}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
Concrete(TypeDescriptor),
|
||||
/// 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>),
|
||||
/// Represents a future which promises to return the inner type.
|
||||
Future(Box<Type>),
|
||||
@@ -367,7 +366,7 @@ impl std::fmt::Debug for Type {
|
||||
Self::Future(ty) => format!("{ty:?}"),
|
||||
};
|
||||
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(),
|
||||
};
|
||||
let result = result.replace("Option<Arc<OwnedContextImpl>>", "Context");
|
||||
write!(f, "{}", result)
|
||||
write!(f, "{result}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,7 @@ impl<'i, T: 'i> Node<'i, ()> for RefCellMutNode<T> {
|
||||
type Output = RefMut<'i, T>;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, _input: ()) -> Self::Output {
|
||||
let a = self.0.borrow_mut();
|
||||
a
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 c = p0.x - x;
|
||||
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 }) => {
|
||||
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 d = p0.x - x;
|
||||
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()
|
||||
|
||||
@@ -307,7 +307,7 @@ mod tests {
|
||||
assert_eq!(bezier.handles, subpath::BezierHandles::Linear);
|
||||
let vector = bezier.start - bezier.end;
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 p0 = transform_point(p0);
|
||||
let p1 = p1.map(|p1| transform_point(p1));
|
||||
let p2 = p2.map(|p2| transform_point(p2));
|
||||
let p1 = p1.map(transform_point);
|
||||
let p2 = p2.map(transform_point);
|
||||
let p3 = transform_point(p3);
|
||||
|
||||
if global_start.is_none() {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
pub mod value;
|
||||
|
||||
use crate::document::value::TaggedValue;
|
||||
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
|
||||
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode};
|
||||
use dyn_any::DynAny;
|
||||
use glam::IVec2;
|
||||
use graphene_core::memo::MemoHashGuard;
|
||||
pub use graphene_core::uuid::NodeId;
|
||||
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 rustc_hash::FxHashMap;
|
||||
use std::collections::HashMap;
|
||||
@@ -44,101 +44,9 @@ pub struct DocumentNode {
|
||||
/// by using network.update_click_target(node_id).
|
||||
#[cfg_attr(target_family = "wasm", serde(alias = "outputs"))]
|
||||
pub inputs: Vec<NodeInput>,
|
||||
/// Manual composition is the methodology by which most nodes are implemented, involving a call argument and upstream inputs.
|
||||
/// By contrast, automatic composition is an alternative way to handle the composition of nodes as they execute in the graph.
|
||||
/// Normally, the program (the compiled graph) builds up its call stack, with each node calling its upstream predecessor to acquire its input data.
|
||||
/// 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>,
|
||||
/// Type of the argument which this node can be evaluated with.
|
||||
#[serde(alias = "manual_composition", default)]
|
||||
pub call_argument: Type,
|
||||
// A nested document network or a proto-node identifier.
|
||||
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.
|
||||
@@ -173,15 +81,13 @@ pub struct OriginalLocation {
|
||||
pub dependants: Vec<Vec<NodeId>>,
|
||||
/// A list of flags indicating whether the input is exposed in the UI
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inputs: Default::default(),
|
||||
manual_composition: Default::default(),
|
||||
call_argument: concrete!(Context),
|
||||
implementation: Default::default(),
|
||||
visible: true,
|
||||
skip_deduplication: Default::default(),
|
||||
@@ -195,14 +101,13 @@ impl Hash for OriginalLocation {
|
||||
self.path.hash(state);
|
||||
self.inputs_source.iter().for_each(|val| val.hash(state));
|
||||
self.inputs_exposed.hash(state);
|
||||
self.skip_inputs.hash(state);
|
||||
}
|
||||
}
|
||||
impl OriginalLocation {
|
||||
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(),
|
||||
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()
|
||||
.flatten()
|
||||
@@ -211,7 +116,7 @@ impl OriginalLocation {
|
||||
}
|
||||
impl DocumentNode {
|
||||
/// 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
|
||||
.inputs
|
||||
.iter()
|
||||
@@ -219,60 +124,38 @@ impl DocumentNode {
|
||||
.nth(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;
|
||||
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 {
|
||||
assert!(!self.inputs.is_empty() || self.manual_composition.is_some(), "Resolving document node {self:#?} with no inputs");
|
||||
fn resolve_proto_node(self) -> ProtoNode {
|
||||
let DocumentNodeImplementation::ProtoNode(identifier) = self.implementation else {
|
||||
unreachable!("tried to resolve not flattened node on resolved node {self:?}");
|
||||
};
|
||||
|
||||
let (input, mut args) = if let Some(ty) = self.manual_composition {
|
||||
(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"),
|
||||
}
|
||||
};
|
||||
let (input, mut args) = (self.call_argument, ConstructionArgs::Nodes(vec![]));
|
||||
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 let &[NodeInput::Inline(ref inline)] = self.inputs.as_slice() {
|
||||
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 {
|
||||
nodes.extend(self.inputs.iter().map(|input| match input {
|
||||
NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda),
|
||||
NodeInput::Node { node_id, .. } => *node_id,
|
||||
_ => unreachable!(),
|
||||
}));
|
||||
}
|
||||
ProtoNode {
|
||||
identifier,
|
||||
input,
|
||||
call_argument: input,
|
||||
construction_args: args,
|
||||
original_location: self.original_location,
|
||||
skip_deduplication: self.skip_deduplication,
|
||||
@@ -284,7 +167,7 @@ impl DocumentNode {
|
||||
#[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub enum NodeInput {
|
||||
/// 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.
|
||||
Value { tagged_value: MemoHash<TaggedValue>, exposed: bool },
|
||||
@@ -323,11 +206,7 @@ pub enum DocumentNodeMetadata {
|
||||
|
||||
impl NodeInput {
|
||||
pub const fn node(node_id: NodeId, output_index: usize) -> Self {
|
||||
Self::Node { node_id, output_index, lambda: false }
|
||||
}
|
||||
|
||||
pub const fn lambda(node_id: NodeId, output_index: usize) -> Self {
|
||||
Self::Node { node_id, output_index, lambda: true }
|
||||
Self::Node { node_id, output_index }
|
||||
}
|
||||
|
||||
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) {
|
||||
if let &mut NodeInput::Node { node_id, output_index, lambda } = self {
|
||||
*self = NodeInput::Node {
|
||||
node_id: f(node_id),
|
||||
output_index,
|
||||
lambda,
|
||||
}
|
||||
if let &mut NodeInput::Node { node_id, output_index } = self {
|
||||
*self = NodeInput::Node { node_id: f(node_id), output_index }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,7 +647,6 @@ impl NodeNetwork {
|
||||
node.original_location = OriginalLocation {
|
||||
path: Some(new_path),
|
||||
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(),
|
||||
..Default::default()
|
||||
};
|
||||
@@ -899,7 +773,7 @@ impl NodeNetwork {
|
||||
|
||||
// Connect layer node to the group below
|
||||
node.inputs.drain(1..);
|
||||
node.manual_composition = None;
|
||||
node.call_argument = concrete!(());
|
||||
self.nodes.insert(id, node);
|
||||
return;
|
||||
}
|
||||
@@ -949,12 +823,11 @@ impl NodeNetwork {
|
||||
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() {
|
||||
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 {
|
||||
// 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 } => {
|
||||
let skip = node.original_location.skip_inputs;
|
||||
nested_node.populate_first_network_input(node_id, output_index, nested_input_index, lambda, node.original_location.inputs(*import_index), skip);
|
||||
NodeInput::Node { node_id, output_index } => {
|
||||
nested_node.populate_first_network_input(node_id, output_index, nested_input_index, node.original_location.inputs(*import_index), 1);
|
||||
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);
|
||||
}
|
||||
@@ -1052,20 +925,10 @@ impl NodeNetwork {
|
||||
*export = NodeInput::Node {
|
||||
node_id: merged_node_id,
|
||||
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> {
|
||||
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 {
|
||||
@@ -1095,7 +958,7 @@ impl NodeNetwork {
|
||||
|
||||
let input_source = &mut output.original_location.inputs_source;
|
||||
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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
|
||||
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode};
|
||||
use std::sync::atomic::AtomicU64;
|
||||
|
||||
fn gen_node_id() -> NodeId {
|
||||
@@ -1319,7 +1182,7 @@ mod test {
|
||||
nodes: [
|
||||
id_node.clone(),
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::lambda(NodeId(0), 0)],
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::Extract,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1365,7 +1228,8 @@ mod test {
|
||||
#[test]
|
||||
fn resolve_proto_node_add() {
|
||||
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()),
|
||||
..Default::default()
|
||||
};
|
||||
@@ -1373,8 +1237,8 @@ mod test {
|
||||
let proto_node = document_node.resolve_proto_node();
|
||||
let reference = ProtoNode {
|
||||
identifier: "graphene_core::structural::ConsNode".into(),
|
||||
input: ProtoNodeInput::ManualComposition(concrete!(u32)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![(NodeId(0), false)]),
|
||||
call_argument: concrete!(u32),
|
||||
construction_args: ConstructionArgs::Nodes(vec![NodeId(0)]),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(proto_node, reference);
|
||||
@@ -1390,13 +1254,12 @@ mod test {
|
||||
NodeId(10),
|
||||
ProtoNode {
|
||||
identifier: "graphene_core::structural::ConsNode".into(),
|
||||
input: ProtoNodeInput::ManualComposition(concrete!(u32)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![(NodeId(14), false)]),
|
||||
call_argument: concrete!(u32),
|
||||
construction_args: ConstructionArgs::Nodes(vec![NodeId(14)]),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(vec![NodeId(1), NodeId(0)]),
|
||||
inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(),
|
||||
inputs_exposed: vec![true, true],
|
||||
skip_inputs: 0,
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -1407,13 +1270,12 @@ mod test {
|
||||
NodeId(11),
|
||||
ProtoNode {
|
||||
identifier: "graphene_core::ops::AddPairNode".into(),
|
||||
input: ProtoNodeInput::Node(NodeId(10)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
call_argument: concrete!(Context),
|
||||
construction_args: ConstructionArgs::Nodes(vec![NodeId(10)]),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(vec![NodeId(1), NodeId(1)]),
|
||||
inputs_source: HashMap::new(),
|
||||
inputs_exposed: vec![true],
|
||||
skip_inputs: 0,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -1423,13 +1285,12 @@ mod test {
|
||||
NodeId(14),
|
||||
ProtoNode {
|
||||
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()),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(vec![NodeId(1), NodeId(4)]),
|
||||
inputs_source: HashMap::new(),
|
||||
inputs_exposed: vec![true, false],
|
||||
skip_inputs: 0,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -1455,13 +1316,13 @@ mod test {
|
||||
(
|
||||
NodeId(10),
|
||||
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()),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(vec![NodeId(1), NodeId(0)]),
|
||||
inputs_source: [(Source { node: vec![NodeId(1)], index: 1 }, 1)].into(),
|
||||
inputs_exposed: vec![true, true],
|
||||
skip_inputs: 0,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -1476,7 +1337,6 @@ mod test {
|
||||
path: Some(vec![NodeId(1), NodeId(4)]),
|
||||
inputs_source: HashMap::new(),
|
||||
inputs_exposed: vec![true, false],
|
||||
skip_inputs: 0,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -1491,7 +1351,6 @@ mod test {
|
||||
path: Some(vec![NodeId(1), NodeId(1)]),
|
||||
inputs_source: HashMap::new(),
|
||||
inputs_exposed: vec![true],
|
||||
skip_inputs: 0,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -1576,49 +1435,4 @@ mod test {
|
||||
}
|
||||
|
||||
// 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");
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -301,13 +301,13 @@ impl TaggedValue {
|
||||
"MAGENTA" => Color::MAGENTA,
|
||||
"TRANSPARENT" => Color::TRANSPARENT,
|
||||
_ => {
|
||||
log::error!("Invalid default value color constant: {}", input);
|
||||
log::error!("Invalid default value color constant: {input}");
|
||||
return None;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
log::error!("Invalid default value color: {}", input);
|
||||
log::error!("Invalid default value color: {input}");
|
||||
None
|
||||
}
|
||||
|
||||
@@ -327,13 +327,13 @@ impl TaggedValue {
|
||||
"BottomCenter" => ReferencePoint::BottomCenter,
|
||||
"BottomRight" => ReferencePoint::BottomRight,
|
||||
_ => {
|
||||
log::error!("Invalid ReferencePoint default type variant: {}", input);
|
||||
log::error!("Invalid ReferencePoint default type variant: {input}");
|
||||
return None;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
log::error!("Invalid ReferencePoint default type: {}", input);
|
||||
log::error!("Invalid ReferencePoint default type: {input}");
|
||||
None
|
||||
}
|
||||
|
||||
@@ -392,7 +392,8 @@ impl<'input> Node<'input, DAny<'input>> for UpcastNode {
|
||||
type Output = FutureAny<'input>;
|
||||
|
||||
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 {
|
||||
|
||||
@@ -37,12 +37,7 @@ impl core::fmt::Display for ProtoNetwork {
|
||||
|
||||
f.write_str(&"\t".repeat(indent + 1))?;
|
||||
f.write_str("Input: ")?;
|
||||
match &node.input {
|
||||
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_fmt(format_args!("Call Argument (type = {:?})", node.call_argument))?;
|
||||
f.write_str("\n")?;
|
||||
|
||||
match &node.construction_args {
|
||||
@@ -52,7 +47,7 @@ impl core::fmt::Display for ProtoNetwork {
|
||||
}
|
||||
ConstructionArgs::Nodes(nodes) => {
|
||||
for id in nodes {
|
||||
write_node(f, network, id.0, indent + 1)?;
|
||||
write_node(f, network, *id, indent + 1)?;
|
||||
}
|
||||
}
|
||||
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`.
|
||||
/// The bool indicates whether to treat the node as lambda node.
|
||||
// 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.
|
||||
Inline(InlineRust),
|
||||
}
|
||||
@@ -121,7 +116,7 @@ impl Hash for ConstructionArgs {
|
||||
impl ConstructionArgs {
|
||||
pub fn new_function_args(&self) -> Vec<String> {
|
||||
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::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.
|
||||
pub struct ProtoNode {
|
||||
pub construction_args: ConstructionArgs,
|
||||
pub input: ProtoNodeInput,
|
||||
pub call_argument: Type,
|
||||
pub identifier: ProtoNodeIdentifier,
|
||||
pub original_location: OriginalLocation,
|
||||
pub skip_deduplication: bool,
|
||||
@@ -144,45 +139,13 @@ impl Default for ProtoNode {
|
||||
Self {
|
||||
identifier: ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
|
||||
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()),
|
||||
input: ProtoNodeInput::None,
|
||||
call_argument: concrete!(()),
|
||||
original_location: OriginalLocation::default(),
|
||||
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 {
|
||||
/// 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).
|
||||
@@ -196,15 +159,8 @@ impl ProtoNode {
|
||||
self.original_location.path.hash(&mut hasher);
|
||||
}
|
||||
|
||||
std::mem::discriminant(&self.input).hash(&mut hasher);
|
||||
match self.input {
|
||||
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),
|
||||
};
|
||||
std::mem::discriminant(&self.call_argument).hash(&mut hasher);
|
||||
self.call_argument.hash(&mut hasher);
|
||||
|
||||
Some(NodeId(hasher.finish()))
|
||||
}
|
||||
@@ -218,7 +174,7 @@ impl ProtoNode {
|
||||
Self {
|
||||
identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"),
|
||||
construction_args: value,
|
||||
input: ProtoNodeInput::ManualComposition(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(path),
|
||||
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.
|
||||
/// 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) {
|
||||
match self.input {
|
||||
ProtoNodeInput::Node(id) => self.input = ProtoNodeInput::Node(f(id)),
|
||||
ProtoNodeInput::NodeLambda(id) => {
|
||||
if !skip_lambdas {
|
||||
self.input = ProtoNodeInput::NodeLambda(f(id))
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {
|
||||
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 {
|
||||
ConstructionArgs::Nodes(nodes) => nodes.clone(),
|
||||
_ => 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>> {
|
||||
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
|
||||
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 {
|
||||
for (ref_id, _) in ref_nodes {
|
||||
for ref_id in ref_nodes {
|
||||
self.check_ref(ref_id, 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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -323,16 +261,8 @@ impl ProtoNetwork {
|
||||
pub fn collect_inwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
|
||||
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
|
||||
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 {
|
||||
for (ref_id, _) in ref_nodes {
|
||||
for ref_id in ref_nodes {
|
||||
self.check_ref(ref_id, 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()];
|
||||
for (node_id, node) in &self.nodes {
|
||||
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 {
|
||||
for (ref_id, _) in ref_nodes {
|
||||
for ref_id in ref_nodes {
|
||||
self.check_ref(ref_id, &NodeId(node_index as u64));
|
||||
inwards_edges[node_index].push(id_map[ref_id]);
|
||||
}
|
||||
@@ -367,70 +290,31 @@ impl ProtoNetwork {
|
||||
(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> {
|
||||
// Perform topological sort once
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Update all of the references to a node ID in the graph with a new ID named `compose_node_id`.
|
||||
fn replace_node_id(&mut self, outwards_edges: &HashMap<NodeId, Vec<NodeId>>, node_id: NodeId, compose_node_id: NodeId, skip_lambdas: bool) {
|
||||
// Update references in other nodes to use the new compose node
|
||||
/// 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, replacement_node_id: NodeId) {
|
||||
// Update references in other nodes to use the new node
|
||||
if let Some(referring_nodes) = outwards_edges.get(&node_id) {
|
||||
for &referring_node_id in referring_nodes {
|
||||
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 {
|
||||
self.output = compose_node_id;
|
||||
self.output = replacement_node_id;
|
||||
}
|
||||
|
||||
self.inputs.iter_mut().for_each(|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() {
|
||||
let mut node = std::mem::take(&mut self.nodes[id.0 as usize].1);
|
||||
// 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));
|
||||
}
|
||||
|
||||
@@ -661,7 +545,6 @@ impl TypingContext {
|
||||
let inputs = match node.construction_args {
|
||||
// If the node has a value input we can infer the return type from it
|
||||
ConstructionArgs::Value(ref v) => {
|
||||
assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context)));
|
||||
// TODO: This should return a reference to the value
|
||||
let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]);
|
||||
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
|
||||
ConstructionArgs::Nodes(ref nodes) => nodes
|
||||
.iter()
|
||||
.map(|(id, _)| {
|
||||
.map(|id| {
|
||||
self.inferred
|
||||
.get(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
|
||||
// TODO: When removing automatic composition, rename this to just `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 call_argument = &node.call_argument;
|
||||
let impls = self.lookup.get(&node.identifier).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?;
|
||||
|
||||
if let Some(index) = inputs.iter().position(|p| {
|
||||
@@ -732,7 +606,7 @@ impl TypingContext {
|
||||
// List of all implementations that match the input types
|
||||
let valid_output_types = impls
|
||||
.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<_>>();
|
||||
|
||||
// Attempt to substitute generic types with concrete types and save the list of results
|
||||
@@ -741,7 +615,7 @@ impl TypingContext {
|
||||
.map(|node_io| {
|
||||
let generics_lookup: Result<HashMap<_, _>, _> = collect_generics(node_io)
|
||||
.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();
|
||||
|
||||
generics_lookup.map(|generics_lookup| {
|
||||
@@ -761,7 +635,7 @@ impl TypingContext {
|
||||
let mut best_errors = usize::MAX;
|
||||
let mut error_inputs = Vec::new();
|
||||
for node_io in impls.keys() {
|
||||
let current_errors = [&primary_input_or_call_argument]
|
||||
let current_errors = [call_argument]
|
||||
.into_iter()
|
||||
.chain(&inputs)
|
||||
.cloned()
|
||||
@@ -770,7 +644,6 @@ impl TypingContext {
|
||||
.filter(|(_, (p1, p2))| !valid_type(p1, p2))
|
||||
.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 = if using_manual_composition { i } else { i + 1 };
|
||||
(i, ty)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -782,15 +655,11 @@ impl TypingContext {
|
||||
error_inputs.push(current_errors);
|
||||
}
|
||||
}
|
||||
let inputs = [&primary_input_or_call_argument]
|
||||
let inputs = [call_argument]
|
||||
.into_iter()
|
||||
.chain(&inputs)
|
||||
.enumerate()
|
||||
// TODO: Make the following line's if statement conditional on being a call argument or primary input
|
||||
.filter_map(|(i, t)| {
|
||||
let i = if using_manual_composition { i } else { i + 1 };
|
||||
if i == 0 { None } else { Some(format!("• Input {i}: {t}")) }
|
||||
})
|
||||
.filter_map(|(i, t)| if i == 0 { None } else { Some(format!("• Input {i}: {t}")) })
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })])
|
||||
@@ -817,13 +686,13 @@ impl TypingContext {
|
||||
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();
|
||||
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();
|
||||
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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
|
||||
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode};
|
||||
|
||||
#[test]
|
||||
fn topological_sort() {
|
||||
@@ -929,16 +798,6 @@ mod test {
|
||||
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]
|
||||
fn stable_node_id_generation() {
|
||||
let mut construction_network = test_network();
|
||||
@@ -948,14 +807,7 @@ mod test {
|
||||
let ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect();
|
||||
assert_eq!(
|
||||
ids,
|
||||
vec![
|
||||
NodeId(16997244687192517417),
|
||||
NodeId(12226224850522777131),
|
||||
NodeId(9162113827627229771),
|
||||
NodeId(12793582657066318419),
|
||||
NodeId(16945623684036608820),
|
||||
NodeId(2640415155091892458)
|
||||
]
|
||||
vec![NodeId(13743208144182721472), NodeId(4607569396187877965), NodeId(16950305885390329527), NodeId(15151181027373658932)]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -968,8 +820,8 @@ mod test {
|
||||
NodeId(7),
|
||||
ProtoNode {
|
||||
identifier: "id".into(),
|
||||
input: ProtoNodeInput::Node(NodeId(11)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
call_argument: concrete!(()),
|
||||
construction_args: ConstructionArgs::Nodes(vec![NodeId(11)]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
@@ -977,8 +829,8 @@ mod test {
|
||||
NodeId(1),
|
||||
ProtoNode {
|
||||
identifier: "id".into(),
|
||||
input: ProtoNodeInput::Node(NodeId(11)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
call_argument: concrete!(()),
|
||||
construction_args: ConstructionArgs::Nodes(vec![NodeId(11)]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
@@ -986,8 +838,8 @@ mod test {
|
||||
NodeId(10),
|
||||
ProtoNode {
|
||||
identifier: "cons".into(),
|
||||
input: ProtoNodeInput::ManualComposition(concrete!(u32)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![(NodeId(14), false)]),
|
||||
call_argument: concrete!(u32),
|
||||
construction_args: ConstructionArgs::Nodes(vec![NodeId(14)]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
@@ -995,8 +847,8 @@ mod test {
|
||||
NodeId(11),
|
||||
ProtoNode {
|
||||
identifier: "add".into(),
|
||||
input: ProtoNodeInput::Node(NodeId(10)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
call_argument: concrete!(()),
|
||||
construction_args: ConstructionArgs::Nodes(vec![NodeId(10)]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
@@ -1004,7 +856,7 @@ mod test {
|
||||
NodeId(14),
|
||||
ProtoNode {
|
||||
identifier: "value".into(),
|
||||
input: ProtoNodeInput::None,
|
||||
call_argument: concrete!(()),
|
||||
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(2).into()),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1024,8 +876,8 @@ mod test {
|
||||
NodeId(1),
|
||||
ProtoNode {
|
||||
identifier: "id".into(),
|
||||
input: ProtoNodeInput::Node(NodeId(2)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
call_argument: concrete!(()),
|
||||
construction_args: ConstructionArgs::Nodes(vec![NodeId(2)]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
@@ -1033,8 +885,8 @@ mod test {
|
||||
NodeId(2),
|
||||
ProtoNode {
|
||||
identifier: "id".into(),
|
||||
input: ProtoNodeInput::Node(NodeId(1)),
|
||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||
call_argument: concrete!(()),
|
||||
construction_args: ConstructionArgs::Nodes(vec![NodeId(1)]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
|
||||
@@ -88,10 +88,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
let device = application_io.gpu_executor().unwrap().context.device.clone();
|
||||
|
||||
let preferences = EditorPreferences {
|
||||
use_vello: true,
|
||||
..Default::default()
|
||||
};
|
||||
let preferences = EditorPreferences { use_vello: true };
|
||||
let editor_api = Arc::new(WasmEditorApi {
|
||||
font_cache: FontCache::default(),
|
||||
application_io: Some(application_io.into()),
|
||||
@@ -104,7 +101,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
match app.command {
|
||||
Command::Compile { print_proto, .. } => {
|
||||
if print_proto {
|
||||
println!("{}", proto_graph);
|
||||
println!("{proto_graph}");
|
||||
}
|
||||
}
|
||||
Command::Run { run_loop, .. } => {
|
||||
@@ -120,7 +117,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
loop {
|
||||
let result = (&executor).execute(render_config).await?;
|
||||
if !run_loop {
|
||||
println!("{:?}", result);
|
||||
println!("{result:?}");
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(16)).await;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use dyn_any::StaticType;
|
||||
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::WasmNotSend;
|
||||
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> {
|
||||
downcast_node(n)
|
||||
}
|
||||
|
||||
@@ -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>> {
|
||||
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
|
||||
|
||||
@@ -150,8 +150,8 @@ pub enum IntrospectError {
|
||||
impl std::fmt::Display for IntrospectError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
IntrospectError::PathNotFound(path) => write!(f, "Path not found: {:?}", path),
|
||||
IntrospectError::ProtoNodeNotFound(id) => write!(f, "ProtoNode not found: {:?}", id),
|
||||
IntrospectError::PathNotFound(path) => write!(f, "Path not found: {path:?}"),
|
||||
IntrospectError::ProtoNodeNotFound(id) => write!(f, "ProtoNode not found: {id:?}"),
|
||||
IntrospectError::NoData => write!(f, "No data found for this node"),
|
||||
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 typing_context = TypingContext::new(&node_registry::NODE_REGISTRY);
|
||||
/// let mut borrow_tree = BorrowTree::new(proto_network, &typing_context).await?;
|
||||
///
|
||||
///
|
||||
/// // Assert that the node exists in the BorrowTree
|
||||
/// assert!(borrow_tree.get(node_id).is_some(), "Node should exist before removal");
|
||||
///
|
||||
///
|
||||
/// // Remove the node
|
||||
/// let removed_path = borrow_tree.free_node(node_id);
|
||||
///
|
||||
///
|
||||
/// // Assert that the node was successfully removed
|
||||
/// 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");
|
||||
///
|
||||
///
|
||||
/// // Try to remove the same node again
|
||||
/// let second_removal = borrow_tree.free_node(node_id);
|
||||
///
|
||||
///
|
||||
/// assert_eq!(second_removal, None, "Second removal should return None");
|
||||
///
|
||||
///
|
||||
/// println!("All assertions passed. free_node function works as expected.");
|
||||
///
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
@@ -393,7 +393,7 @@ impl BorrowTree {
|
||||
}
|
||||
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
|
||||
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 constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||
let node = constructor(construction_nodes).await;
|
||||
|
||||
@@ -19,7 +19,8 @@ mod tests {
|
||||
(
|
||||
NodeId(0),
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(u32), 0)],
|
||||
inputs: vec![],
|
||||
call_argument: concrete!(u32),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -8,15 +8,15 @@ use graphene_core::raster::*;
|
||||
#[cfg(feature = "gpu")]
|
||||
use graphene_core::raster_types::GPU;
|
||||
use graphene_core::raster_types::{CPU, Raster};
|
||||
use graphene_core::{Artboard, concrete, generic};
|
||||
use graphene_core::{Cow, ProtoNodeIdentifier, Type};
|
||||
use graphene_core::{Artboard, concrete};
|
||||
use graphene_core::{Cow, ProtoNodeIdentifier};
|
||||
use graphene_core::{NodeIO, NodeIOTypes};
|
||||
use graphene_core::{fn_type_fut, future};
|
||||
use graphene_std::Context;
|
||||
use graphene_std::Graphic;
|
||||
#[cfg(feature = "gpu")]
|
||||
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::brush::brush_cache::BrushCache;
|
||||
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 => Option<WgpuSurface>]),
|
||||
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
|
||||
// =======================
|
||||
@@ -294,17 +275,23 @@ mod node_registry_macros {
|
||||
(from: $from:ty, to: $to:ty) => {
|
||||
(
|
||||
ProtoNodeIdentifier::new(concat!["graphene_core::ops::IntoNode<", stringify!($to), ">"]),
|
||||
|_| {
|
||||
|mut args| {
|
||||
Box::pin(async move {
|
||||
let node = graphene_core::ops::IntoNode::<$to>::new();
|
||||
let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
let node = graphene_core::ops::IntoNode::new(
|
||||
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
|
||||
})
|
||||
},
|
||||
{
|
||||
let node = graphene_core::ops::IntoNode::<$to>::new();
|
||||
let mut node_io = NodeIO::<'_, $from>::to_async_node_io(&node, vec![]);
|
||||
node_io.call_argument = future!(<$from as StaticType>::Static);
|
||||
let node = graphene_core::ops::IntoNode::new(
|
||||
graphene_std::any::PanicNode::<Context, core::pin::Pin<Box<dyn core::future::Future<Output = $from> + Send>>>::new(),
|
||||
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
|
||||
},
|
||||
)
|
||||
@@ -333,17 +320,20 @@ mod node_registry_macros {
|
||||
(from: $from:ty, to: $to:ty) => {
|
||||
(
|
||||
ProtoNodeIdentifier::new(concat!["graphene_core::ops::ConvertNode<", stringify!($to), ">"]),
|
||||
|_| {
|
||||
|mut args| {
|
||||
Box::pin(async move {
|
||||
let node = graphene_core::ops::ConvertNode::<$to>::new();
|
||||
let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
let node = graphene_core::ops::ConvertNode::new(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
|
||||
})
|
||||
},
|
||||
{
|
||||
let node = graphene_core::ops::ConvertNode::<$to>::new();
|
||||
let mut node_io = NodeIO::<'_, $from>::to_async_node_io(&node, vec![]);
|
||||
node_io.call_argument = future!(<$from as StaticType>::Static);
|
||||
let node = graphene_core::ops::ConvertNode::new(graphene_std::any::PanicNode::<Context, core::pin::Pin<Box<dyn core::future::Future<Output = $from> + Send>>>::new(),
|
||||
|
||||
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
|
||||
},
|
||||
)
|
||||
|
||||
@@ -30,20 +30,20 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memo::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
// TODO: Add conversion step
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(graphene_std::application_io::RenderConfig)),
|
||||
call_argument: concrete!(graphene_std::application_io::RenderConfig),
|
||||
inputs: vec![
|
||||
NodeInput::scope("editor-api"),
|
||||
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(generic!(T))), 0),
|
||||
|
||||
@@ -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 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);
|
||||
Ok(quote! {
|
||||
/// Underlying implementation for [#struct_name]
|
||||
@@ -323,6 +323,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||
|
||||
#cfg
|
||||
#[doc(hidden)]
|
||||
#[allow(clippy::module_inception)]
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
use #graphene_core as gcore;
|
||||
|
||||
@@ -117,7 +117,7 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
|
||||
.map_err(|e| {
|
||||
syn::Error::new(
|
||||
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 {
|
||||
|
||||
@@ -295,8 +295,8 @@ impl Parse for NodeFnAttributes {
|
||||
}
|
||||
|
||||
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 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 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: {e}. Make sure it's a valid Rust function.")))?;
|
||||
|
||||
let vis = input_fn.vis;
|
||||
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| {
|
||||
Error::new(
|
||||
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
|
||||
@@ -405,7 +405,7 @@ fn parse_implementations(attr: &Attribute, name: &Ident) -> syn::Result<Punctuat
|
||||
let parser = Punctuated::<Type, Comma>::parse_terminated;
|
||||
parser.parse2(content.clone()).map_err(|e| {
|
||||
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 default_value = extract_attribute(attrs, "default")
|
||||
.map(|attr| {
|
||||
attr.parse_args()
|
||||
.map_err(|e| Error::new_spanned(attr, format!("Invalid `default` value for argument '{}': {}", ident, e)))
|
||||
})
|
||||
.map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `default` value for argument '{ident}': {e}"))))
|
||||
.transpose()?;
|
||||
|
||||
let scope = extract_attribute(attrs, "scope")
|
||||
.map(|attr| {
|
||||
attr.parse_args()
|
||||
.map_err(|e| Error::new_spanned(attr, format!("Invalid `scope` value for argument '{}': {}", ident, e)))
|
||||
})
|
||||
.map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `scope` value for argument '{ident}': {e}"))))
|
||||
.transpose()?;
|
||||
|
||||
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()?;
|
||||
|
||||
let widget_override = extract_attribute(attrs, "widget")
|
||||
.map(|attr| {
|
||||
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()?
|
||||
.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")
|
||||
.map(|attr| {
|
||||
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()?;
|
||||
let number_soft_max = extract_attribute(attrs, "soft_max")
|
||||
.map(|attr| {
|
||||
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()?;
|
||||
|
||||
let number_hard_min = extract_attribute(attrs, "hard_min")
|
||||
.map(|attr| {
|
||||
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()?;
|
||||
let number_hard_max = extract_attribute(attrs, "hard_max")
|
||||
.map(|attr| {
|
||||
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()?;
|
||||
|
||||
@@ -496,10 +490,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||
attr.parse_args::<ExprTuple>().map_err(|e| {
|
||||
Error::new_spanned(
|
||||
attr,
|
||||
format!(
|
||||
"Invalid `range` tuple of min and max range slider values for argument '{}': {}\nUSAGE EXAMPLE: #[range((0., 100.))]",
|
||||
ident, e
|
||||
),
|
||||
format!("Invalid `range` tuple of min and max range slider values for argument '{ident}': {e}\nUSAGE EXAMPLE: #[range((0., 100.))]"),
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -511,7 +502,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||
}
|
||||
|
||||
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()?;
|
||||
|
||||
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| {
|
||||
Error::new_spanned(
|
||||
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()?
|
||||
.map(|f| {
|
||||
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 {
|
||||
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")
|
||||
.map(|attr| {
|
||||
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()?;
|
||||
|
||||
@@ -660,7 +651,7 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
|
||||
Ok(parsed) => parsed,
|
||||
Err(e) => {
|
||||
// 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"),
|
||||
}
|
||||
assert_eq!(format!("{:?}", p_ty), format!("{:?}", e_ty));
|
||||
assert_eq!(format!("{p_ty:?}"), format!("{:?}", e_ty));
|
||||
}
|
||||
(
|
||||
ParsedField {
|
||||
@@ -780,8 +771,8 @@ mod tests {
|
||||
},
|
||||
) => {
|
||||
assert_eq!(p_name, e_name);
|
||||
assert_eq!(format!("{:?}", p_input), format!("{:?}", e_input));
|
||||
assert_eq!(format!("{:?}", p_output), format!("{:?}", e_output));
|
||||
assert_eq!(format!("{p_input:?}"), format!("{:?}", e_input));
|
||||
assert_eq!(format!("{p_output:?}"), format!("{:?}", e_output));
|
||||
}
|
||||
_ => panic!("Mismatched field types"),
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
|
||||
(
|
||||
NodeId(i as u64),
|
||||
match inputs.len() {
|
||||
1 if false => {
|
||||
1 => {
|
||||
let input = inputs.iter().next().unwrap();
|
||||
let input_ty = input.nested_type();
|
||||
|
||||
@@ -84,9 +84,9 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
|
||||
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(input.clone(), i)],
|
||||
// manual_composition: Some(fn_input.clone()),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(proto_node),
|
||||
visible: true,
|
||||
call_argument: concrete!(Context),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -107,8 +107,8 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
|
||||
|
||||
let document_node = DocumentNode {
|
||||
inputs: network_inputs,
|
||||
manual_composition: Some(input_type.clone()),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()),
|
||||
call_argument: input_type.clone(),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
|
||||
visible: true,
|
||||
skip_deduplication: false,
|
||||
..Default::default()
|
||||
@@ -118,12 +118,11 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
|
||||
|
||||
let node = DocumentNode {
|
||||
inputs,
|
||||
manual_composition: Some(input_type.clone()),
|
||||
call_argument: input_type.clone(),
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::Node {
|
||||
node_id: NodeId(input_count as u64),
|
||||
output_index: 0,
|
||||
lambda: false,
|
||||
}],
|
||||
nodes,
|
||||
scope_injections: Default::default(),
|
||||
|
||||
@@ -101,14 +101,11 @@ pub fn derive_discriminant_impl(input_item: TokenStream) -> syn::Result<TokenStr
|
||||
let (pattern, value) = is_sub_discriminant
|
||||
.into_iter()
|
||||
.map(|b| {
|
||||
(
|
||||
if b {
|
||||
quote::quote! { (x) }
|
||||
} else {
|
||||
quote::quote! { { .. } }
|
||||
},
|
||||
b.then(|| quote::quote! { (x.to_discriminant()) }).unwrap_or_default(),
|
||||
)
|
||||
if b {
|
||||
(quote::quote! {(x)}, quote::quote! {(x.to_discriminant())})
|
||||
} else {
|
||||
(quote::quote! {{..}}, Default::default())
|
||||
}
|
||||
})
|
||||
.unzip::<_, _, Vec<_>, Vec<_>>();
|
||||
#[cfg(feature = "serde-discriminant")]
|
||||
|
||||
@@ -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.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.push('>');
|
||||
@@ -74,7 +74,7 @@ pub fn clean_rust_type_syntax(input: String) -> String {
|
||||
}
|
||||
'-' => {
|
||||
if let Some('>') = chars.peek() {
|
||||
while let Some(' ') = result.chars().rev().next() {
|
||||
while let Some(' ') = result.chars().next_back() {
|
||||
result.pop();
|
||||
}
|
||||
result.push_str(" -> ");
|
||||
@@ -88,7 +88,7 @@ pub fn clean_rust_type_syntax(input: String) -> String {
|
||||
}
|
||||
':' => {
|
||||
if let Some(':') = chars.peek() {
|
||||
while let Some(' ') = result.chars().rev().next() {
|
||||
while let Some(' ') = result.chars().next_back() {
|
||||
result.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,22 +47,20 @@ pub fn generate_hierarchical_tree(input: TokenStream) -> syn::Result<TokenStream
|
||||
})
|
||||
} else {
|
||||
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 => {
|
||||
let field_type = &fields.unnamed.first().unwrap().ty;
|
||||
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).",
|
||||
variant_type,
|
||||
field_type.to_token_stream()
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let field_types = fields.unnamed.iter().map(|f| f.ty.to_token_stream().to_string()).collect::<Vec<_>>().join(", ");
|
||||
format!(
|
||||
"The `{}` 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).",
|
||||
variant_type, field_types
|
||||
"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 `{field_types}` with named fields using {{curly braces}} instead of positional fields using (parentheses)."
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 -->
|
||||
<!-- 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 -->
|
||||
<!-- Cache nodes and stable node IDs -->
|
||||
<!-- Graph rewriting step (currently used only to remove Identity nodes),
|
||||
|
||||
Reference in New Issue
Block a user