mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 06:38:03 +08:00
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:
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user