Fix Imaginate by porting its JS roundtrip code to graph-based async execution in Rust (#1250)

* Create asynchronous rust imaginate node

* Make a first imaginate request via rust

* Implement parsing of imaginate API result image

* Stop refresh timer from affecting imaginate progress requests

* Add cargo-about clarification for rustls-webpki

* Delete imaginate.ts and all uses of its functions

* Add imaginate img2img feature

* Fix imaginate random seed button

* Fix imaginate ui inferring non-custom resolutions

* Fix the imaginate progress indicator

* Remove ImaginatePreferences from being compiled into node graph

* Regenerate imaginate only when hitting button

* Add ability to terminate imaginate requests

* Add imaginate server check feature

* Do not compile wasm_bindgen bindings in graphite_editor for tests

* Address some review suggestions

- move wasm futures dependency in editor to the future-executor crate
- guard wasm-bindgen in editor behind a `wasm` feature flag
- dont make seed number input a slider
- remove poll_server_check from process_message function beginning
- guard wasm related code behind `cfg(target_arch = "wasm32")` instead
  of `cfg(test)`
- Call the imaginate idle states "Ready" and "Done" instead of "Nothing
  to do"
- Call the imaginate uploading state "Uploading Image" instead of
  "Uploading Input Image"
- Remove the EvalSyncNode

* Fix imaginate host name being restored between graphite instances

also change the progress status texts a bit.

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
nat-rix
2023-06-09 09:03:15 +02:00
committed by Keavon Chambers
parent a1c70c4d90
commit f76b850b9c
35 changed files with 1500 additions and 1326 deletions

View File

@@ -10,16 +10,16 @@ use document_legacy::{LayerId, Operation};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
use graph_craft::graphene_compiler::Compiler;
use graph_craft::imaginate_input::ImaginatePreferences;
use graph_craft::{concrete, Type, TypeDescriptor};
use graphene_core::application_io::ApplicationIo;
use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender};
use graphene_core::raster::{Image, ImageFrame};
use graphene_core::renderer::{SvgSegment, SvgSegmentList};
use graphene_core::text::FontCache;
use graphene_core::vector::style::ViewMode;
use graphene_core::{Color, SurfaceFrame, SurfaceId};
use graphene_std::wasm_application_io::WasmApplicationIo;
use graphene_std::wasm_application_io::WasmEditorApi;
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::DynamicExecutor;
use glam::{DAffine2, DVec2};
@@ -33,8 +33,9 @@ pub struct NodeRuntime {
pub(crate) executor: DynamicExecutor,
font_cache: FontCache,
receiver: Receiver<NodeRuntimeMessage>,
sender: Sender<GenerationResponse>,
sender: InternalNodeGraphUpdateSender,
wasm_io: Option<WasmApplicationIo>,
imaginate_preferences: ImaginatePreferences,
pub(crate) thumbnails: HashMap<LayerId, HashMap<NodeId, SvgSegmentList>>,
canvas_cache: HashMap<Vec<LayerId>, SurfaceId>,
}
@@ -42,6 +43,7 @@ pub struct NodeRuntime {
enum NodeRuntimeMessage {
GenerationRequest(GenerationRequest),
FontCacheUpdate(FontCache),
ImaginatePreferencesUpdate(ImaginatePreferences),
}
pub(crate) struct GenerationRequest {
@@ -50,6 +52,7 @@ pub(crate) struct GenerationRequest {
path: Vec<LayerId>,
image_frame: Option<ImageFrame<Color>>,
}
pub(crate) struct GenerationResponse {
generation_id: u64,
result: Result<TaggedValue, String>,
@@ -57,18 +60,38 @@ pub(crate) struct GenerationResponse {
new_thumbnails: HashMap<LayerId, HashMap<NodeId, SvgSegmentList>>,
}
enum NodeGraphUpdate {
GenerationResponse(GenerationResponse),
NodeGraphUpdateMessage(NodeGraphUpdateMessage),
}
struct InternalNodeGraphUpdateSender(Sender<NodeGraphUpdate>);
impl InternalNodeGraphUpdateSender {
fn send_generation_response(&self, response: GenerationResponse) {
self.0.send(NodeGraphUpdate::GenerationResponse(response)).expect("Failed to send response")
}
}
impl NodeGraphUpdateSender for InternalNodeGraphUpdateSender {
fn send(&self, message: NodeGraphUpdateMessage) {
self.0.send(NodeGraphUpdate::NodeGraphUpdateMessage(message)).expect("Failed to send response")
}
}
thread_local! {
pub(crate) static NODE_RUNTIME: Rc<RefCell<Option<NodeRuntime>>> = Rc::new(RefCell::new(None));
}
impl NodeRuntime {
fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<GenerationResponse>) -> Self {
fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self {
let executor = DynamicExecutor::default();
Self {
executor,
receiver,
sender,
sender: InternalNodeGraphUpdateSender(sender),
font_cache: FontCache::default(),
imaginate_preferences: Default::default(),
thumbnails: Default::default(),
wasm_io: None,
canvas_cache: Default::default(),
@@ -80,13 +103,14 @@ impl NodeRuntime {
// This should be avoided in the future.
requests.reverse();
requests.dedup_by_key(|x| match x {
NodeRuntimeMessage::FontCacheUpdate(_) => None,
NodeRuntimeMessage::GenerationRequest(x) => Some(x.path.clone()),
_ => None,
});
requests.reverse();
for request in requests {
match request {
NodeRuntimeMessage::FontCacheUpdate(font_cache) => self.font_cache = font_cache,
NodeRuntimeMessage::ImaginatePreferencesUpdate(preferences) => self.imaginate_preferences = preferences,
NodeRuntimeMessage::GenerationRequest(GenerationRequest {
generation_id,
graph,
@@ -105,7 +129,7 @@ impl NodeRuntime {
updates: responses,
new_thumbnails: self.thumbnails.clone(),
};
self.sender.send(response).expect("Failed to send response");
self.sender.send_generation_response(response);
}
}
}
@@ -134,6 +158,8 @@ impl NodeRuntime {
font_cache: &self.font_cache,
image_frame,
application_io: &self.wasm_io.as_ref().unwrap(),
node_graph_message_sender: &self.sender,
imaginate_preferences: &self.imaginate_preferences,
};
// We assume only one output
@@ -240,7 +266,7 @@ pub async fn run_node_graph() {
#[derive(Debug)]
pub struct NodeGraphExecutor {
sender: Sender<NodeRuntimeMessage>,
receiver: Receiver<GenerationResponse>,
receiver: Receiver<NodeGraphUpdate>,
// TODO: This is a memory leak since layers are never removed
pub(crate) last_output_type: HashMap<Vec<LayerId>, Option<Type>>,
pub(crate) thumbnails: HashMap<LayerId, HashMap<NodeId, SvgSegmentList>>,
@@ -294,10 +320,31 @@ impl NodeGraphExecutor {
self.sender.send(NodeRuntimeMessage::FontCacheUpdate(font_cache)).expect("Failed to send font cache update");
}
pub fn update_imaginate_preferences(&self, imaginate_preferences: ImaginatePreferences) {
self.sender
.send(NodeRuntimeMessage::ImaginatePreferencesUpdate(imaginate_preferences))
.expect("Failed to send imaginate preferences");
}
pub fn previous_output_type(&self, path: &[LayerId]) -> Option<Type> {
self.last_output_type.get(path).cloned().flatten()
}
pub fn introspect_node_in_network<T: std::any::Any + core::fmt::Debug, U, F1: FnOnce(&NodeNetwork) -> Option<NodeId>, F2: FnOnce(&T) -> U>(
&mut self,
network: &NodeNetwork,
node_path: &[NodeId],
find_node: F1,
extract_data: F2,
) -> Option<U> {
let wrapping_document_node = network.nodes.get(node_path.last()?)?;
let DocumentNodeImplementation::Network(wrapped_network) = &wrapping_document_node.implementation else { return None; };
let introspection_node = find_node(&wrapped_network)?;
let introspection = self.introspect_node(&[node_path, &[introspection_node]].concat())?;
let downcasted: &T = <dyn std::any::Any>::downcast_ref(introspection.as_ref())?;
Some(extract_data(downcasted))
}
/// Encodes an image into a format using the image crate
fn encode_img(image: Image<Color>, resize: Option<DVec2>, format: image::ImageOutputFormat) -> Result<(Vec<u8>, (u32, u32)), String> {
use image::{ImageBuffer, Rgba};
@@ -334,13 +381,12 @@ impl NodeGraphExecutor {
})
}
/// Evaluates a node graph, computing either the Imaginate node or the entire graph
/// Evaluates a node graph, computing the entire graph
pub fn submit_node_graph_evaluation(
&mut self,
(document_id, documents): (u64, &mut HashMap<u64, DocumentMessageHandler>),
layer_path: Vec<LayerId>,
(input_image_data, (width, height)): (Vec<u8>, (u32, u32)),
_imaginate_node: Option<Vec<NodeId>>,
_persistent_data: (&PreferencesMessageHandler, &PersistentData),
_responses: &mut VecDeque<Message>,
) -> Result<(), String> {
@@ -365,11 +411,6 @@ impl NodeGraphExecutor {
let transform = DAffine2::IDENTITY;
let image_frame = ImageFrame { image, transform };
// Special execution path for generating Imaginate (as generation requires IO from outside node graph)
/*if let Some(imaginate_node) = imaginate_node {
responses.add(self.generate_imaginate(network, imaginate_node, (document, document_id), layer_path, editor_api, persistent_data)?);
return Ok(());
}*/
// Execute the node graph
let generation_id = self.queue_execution(network, Some(image_frame), layer_path.clone());
@@ -381,26 +422,32 @@ impl NodeGraphExecutor {
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
let results = self.receiver.try_iter().collect::<Vec<_>>();
for response in results {
let GenerationResponse {
generation_id,
result,
updates,
new_thumbnails,
} = response;
self.thumbnails = new_thumbnails;
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?;
let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
responses.extend(updates);
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?;
responses.add(DocumentMessage::LayerChanged {
affected_layer_path: execution_context.layer_path,
});
responses.add(DocumentMessage::RenderDocument);
responses.add(ArtboardMessage::RenderArtboards);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(DocumentMessage::DirtyRenderDocument);
responses.add(DocumentMessage::Overlays(OverlaysMessage::Rerender));
match response {
NodeGraphUpdate::GenerationResponse(GenerationResponse {
generation_id,
result,
updates,
new_thumbnails,
}) => {
self.thumbnails = new_thumbnails;
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?;
let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
responses.extend(updates);
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?;
responses.add(DocumentMessage::LayerChanged {
affected_layer_path: execution_context.layer_path,
});
responses.add(DocumentMessage::RenderDocument);
responses.add(ArtboardMessage::RenderArtboards);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(DocumentMessage::DirtyRenderDocument);
responses.add(DocumentMessage::Overlays(OverlaysMessage::Rerender));
}
NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => {
responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::ResendActiveProperties))
}
}
}
Ok(())
}