Add handler for deferred execution of messages (#2951)

* Add Handler for defered execution of messages

* Cleanup

* Track graph execution id to associate messages with their corresponding execution id

* Rename ViewportReady -> NavigationReady

* Defer layer deselection
This commit is contained in:
Dennis Kobert 2025-07-29 01:57:11 +02:00 committed by GitHub
parent 2247dd9818
commit 35ab266bbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 182 additions and 131 deletions

View file

@ -5,7 +5,6 @@ use crate::messages::prelude::*;
#[derive(Debug, Default)]
pub struct Dispatcher {
buffered_queue: Option<Vec<VecDeque<Message>>>,
message_queues: Vec<VecDeque<Message>>,
pub responses: Vec<FrontendMessage>,
pub message_handlers: DispatcherMessageHandlers,
@ -17,6 +16,7 @@ pub struct DispatcherMessageHandlers {
app_window_message_handler: AppWindowMessageHandler,
broadcast_message_handler: BroadcastMessageHandler,
debug_message_handler: DebugMessageHandler,
defer_message_handler: DeferMessageHandler,
dialog_message_handler: DialogMessageHandler,
globals_message_handler: GlobalsMessageHandler,
input_preprocessor_message_handler: InputPreprocessorMessageHandler,
@ -51,7 +51,10 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
];
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame))];
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame)),
MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),
];
// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];
@ -91,14 +94,6 @@ impl Dispatcher {
pub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {
let message = message.into();
// Add all additional messages to the buffer if it exists (except from the end buffer message)
if !matches!(message, Message::EndBuffer { .. }) {
if let Some(buffered_queue) = &mut self.buffered_queue {
Self::schedule_execution(buffered_queue, true, [message]);
return;
}
}
// If we are not maintaining the buffer, simply add to the current queue
Self::schedule_execution(&mut self.message_queues, process_after_all_current, [message]);
@ -137,6 +132,9 @@ impl Dispatcher {
Message::Debug(message) => {
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
}
Message::Defer(message) => {
self.message_handlers.defer_message_handler.process_message(message, &mut queue, ());
}
Message::Dialog(message) => {
let context = DialogMessageContext {
portfolio: &self.message_handlers.portfolio_message_handler,
@ -232,37 +230,6 @@ impl Dispatcher {
Message::Batched { messages } => {
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
}
Message::StartBuffer => {
self.buffered_queue = Some(std::mem::take(&mut self.message_queues));
}
Message::EndBuffer { render_metadata } => {
// Assign the message queue to the currently buffered queue
if let Some(buffered_queue) = self.buffered_queue.take() {
self.cleanup_queues(false);
assert!(self.message_queues.is_empty(), "message queues are always empty when ending a buffer");
self.message_queues = buffered_queue;
};
let graphene_std::renderer::RenderMetadata {
upstream_footprints: footprints,
local_transforms,
first_instance_source_id,
click_targets,
clip_targets,
} = render_metadata;
// Run these update state messages immediately
let messages = [
DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints: footprints,
local_transforms,
first_instance_source_id,
},
DocumentMessage::UpdateClickTargets { click_targets },
DocumentMessage::UpdateClipTargets { clip_targets },
];
Self::schedule_execution(&mut self.message_queues, false, messages.map(Message::from));
}
}
// If there are child messages, append the queue to the list of queues

View file

@ -0,0 +1,10 @@
use crate::messages::prelude::*;
#[impl_message(Message, Defer)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum DeferMessage {
TriggerGraphRun(u64),
AfterGraphRun { messages: Vec<Message> },
TriggerNavigationReady,
AfterNavigationReady { messages: Vec<Message> },
}

View file

@ -0,0 +1,36 @@
use crate::messages::prelude::*;
#[derive(Debug, Default, ExtractField)]
pub struct DeferMessageHandler {
after_graph_run: Vec<(u64, Message)>,
after_viewport_resize: Vec<Message>,
current_graph_submission_id: u64,
}
#[message_handler_data]
impl MessageHandler<DeferMessage, ()> for DeferMessageHandler {
fn process_message(&mut self, message: DeferMessage, responses: &mut VecDeque<Message>, _: ()) {
match message {
DeferMessage::AfterGraphRun { mut messages } => {
self.after_graph_run.extend(messages.drain(..).map(|m| (self.current_graph_submission_id, m)));
}
DeferMessage::AfterNavigationReady { messages } => {
self.after_viewport_resize.extend_from_slice(&messages);
}
DeferMessage::TriggerGraphRun(execution_id) => {
self.current_graph_submission_id = execution_id;
for message in self.after_graph_run.extract_if(.., |x| x.0 < self.current_graph_submission_id) {
responses.push_front(message.1);
}
}
DeferMessage::TriggerNavigationReady => {
for message in self.after_viewport_resize.drain(..) {
responses.push_front(message);
}
}
}
}
advertise_actions!(DeferMessageDiscriminant;
);
}

View file

@ -0,0 +1,7 @@
mod defer_message;
mod defer_message_handler;
#[doc(inline)]
pub use defer_message::{DeferMessage, DeferMessageDiscriminant};
#[doc(inline)]
pub use defer_message_handler::DeferMessageHandler;

View file

@ -24,18 +24,21 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;
if create_artboard {
responses.add(Message::StartBuffer);
responses.add(GraphOperationMessage::NewArtboard {
id: NodeId::new(),
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(DeferMessage::AfterGraphRun {
messages: vec![
GraphOperationMessage::NewArtboard {
id: NodeId::new(),
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
}
.into(),
],
});
}
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
responses.add(Message::StartBuffer);
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
responses.add(DocumentMessage::DeselectAllLayers);
responses.add(DeferMessage::AfterNavigationReady {
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into(), DocumentMessage::DeselectAllLayers.into()],
});
}
}

View file

@ -59,7 +59,6 @@ pub enum FrontendMessage {
#[serde(rename = "commitDate")]
commit_date: String,
},
TriggerDelayedZoomCanvasToFitAll,
TriggerDownloadImage {
svg: String,
name: String,

View file

@ -1,5 +1,4 @@
use crate::messages::prelude::*;
use graphene_std::renderer::RenderMetadata;
use graphite_proc_macros::*;
#[impl_message]
@ -15,6 +14,8 @@ pub enum Message {
#[child]
Debug(DebugMessage),
#[child]
Defer(DeferMessage),
#[child]
Dialog(DialogMessage),
#[child]
Frontend(FrontendMessage),
@ -40,10 +41,6 @@ pub enum Message {
Batched {
messages: Box<[Message]>,
},
StartBuffer,
EndBuffer {
render_metadata: RenderMetadata,
},
}
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.

View file

@ -4,6 +4,7 @@ pub mod animation;
pub mod app_window;
pub mod broadcast;
pub mod debug;
pub mod defer;
pub mod dialog;
pub mod frontend;
pub mod globals;

View file

@ -1435,6 +1435,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
},
})
}
// Some parts of the editior (e.g. navigation messages) depend on these bounds to be present
let bounds = if self.graph_view_overlay_open {
self.network_interface.all_nodes_bounding_box(&self.breadcrumb_network_path).cloned()
} else {
self.network_interface.document_bounds_document_space(true)
};
if bounds.is_some() {
responses.add(DeferMessage::TriggerNavigationReady);
} else {
// If we don't have bounds yet, we need wait until the node graph has run once more
responses.add(DeferMessage::AfterGraphRun {
messages: vec![DocumentMessage::PTZUpdate.into()],
});
}
}
DocumentMessage::SelectionStepBack => {
self.network_interface.selection_step_back(&self.selection_network_path);
@ -1866,14 +1880,14 @@ impl DocumentMessageHandler {
let previous_network = std::mem::replace(&mut self.network_interface, network_interface);
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(Message::StartBuffer);
responses.push_front(NodeGraphMessage::UnloadWires.into());
responses.push_front(NodeGraphMessage::SetGridAlignedEdges.into());
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.push_front(NodeGraphMessage::ForceRunDocumentGraph.into());
responses.push_front(NodeGraphMessage::SelectedNodesUpdated.into());
responses.push_front(PortfolioMessage::UpdateOpenDocumentsList.into());
Some(previous_network)
}
pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {

View file

@ -568,8 +568,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all_new_ids });
responses.add(Message::StartBuffer);
responses.add(PortfolioMessage::CenterPastedLayers { layers });
responses.add(DeferMessage::AfterGraphRun {
messages: vec![PortfolioMessage::CenterPastedLayers { layers }.into()],
});
}
}
}
@ -701,13 +702,12 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
if create_document {
// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image
responses.add(Message::StartBuffer);
responses.add(DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true });
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
responses.add(Message::StartBuffer);
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
responses.add(DeferMessage::AfterNavigationReady {
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into()],
});
responses.add(DeferMessage::AfterGraphRun {
messages: vec![DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }.into()],
});
}
}
PortfolioMessage::PasteSvg {
@ -733,13 +733,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
if create_document {
// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image
responses.add(Message::StartBuffer);
responses.add(DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true });
responses.add(DeferMessage::AfterGraphRun {
messages: vec![DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }.into()],
});
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
responses.add(Message::StartBuffer);
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
responses.add(DeferMessage::AfterNavigationReady {
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into()],
});
}
}
PortfolioMessage::PrevDocument => {
@ -1019,9 +1019,6 @@ impl PortfolioMessageHandler {
/text>"#
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
.to_string();
responses.add(Message::EndBuffer {
render_metadata: graphene_std::renderer::RenderMetadata::default(),
});
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
}
result

View file

@ -6,6 +6,7 @@ pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscrimin
pub use crate::messages::app_window::{AppWindowMessage, AppWindowMessageDiscriminant, AppWindowMessageHandler};
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
pub use crate::messages::defer::{DeferMessage, DeferMessageDiscriminant, DeferMessageHandler};
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
pub use crate::messages::dialog::new_document_dialog::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant, NewDocumentDialogMessageHandler};
pub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage, PreferencesDialogMessageContext, PreferencesDialogMessageDiscriminant, PreferencesDialogMessageHandler};

View file

@ -153,8 +153,9 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(Message::StartBuffer);
responses.add(PenToolMessage::RecalculateLatestPointsPosition);
responses.add(DeferMessage::AfterGraphRun {
messages: vec![PenToolMessage::RecalculateLatestPointsPosition.into()],
});
}
/// Merge the `first_endpoint` with `second_endpoint`.

View file

@ -383,8 +383,9 @@ impl Fsm for BrushToolFsmState {
else {
new_brush_layer(document, responses);
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(Message::StartBuffer);
responses.add(BrushToolMessage::DragStart);
responses.add(DeferMessage::AfterGraphRun {
messages: vec![BrushToolMessage::DragStart.into()],
});
BrushToolFsmState::Ready
}
}

View file

@ -251,9 +251,12 @@ impl Fsm for FreehandToolFsmState {
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
responses.add(Message::StartBuffer);
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
let defered_responses = &mut VecDeque::new();
tool_options.fill.apply_fill(layer, defered_responses);
tool_options.stroke.apply_stroke(tool_data.weight, layer, defered_responses);
responses.add(DeferMessage::AfterGraphRun {
messages: defered_responses.drain(..).collect(),
});
tool_data.layer = Some(layer);
FreehandToolFsmState::Drawing

View file

@ -1257,10 +1257,10 @@ impl PenToolData {
self.prior_segments = None;
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
// This causes the following message to be run only after the next graph evaluation runs and the transforms are updated
responses.add(Message::StartBuffer);
// It is necessary to defer this until the transform of the layer can be accurately computed (quite hacky)
responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport });
responses.add(DeferMessage::AfterGraphRun {
messages: vec![PenToolMessage::AddPointLayerPosition { layer, viewport }.into()],
});
}
/// Perform extension of an existing path
@ -1721,9 +1721,9 @@ impl Fsm for PenToolFsmState {
let next_point = tool_data.next_point;
let start = latest_point.id;
if let Some(layer) = layer {
let mut vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
if let Some(layer) = layer
&& let Some(mut vector_data) = document.network_interface.compute_modified_vector(layer)
{
let closest_point = vector_data.extendable_points(preferences.vector_meshes).filter(|&id| id != start).find(|&id| {
vector_data.point_domain.position_from_id(id).map_or(false, |pos| {
let dist_sq = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));

View file

@ -624,29 +624,33 @@ impl Fsm for ShapeToolFsmState {
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses);
responses.add(Message::StartBuffer);
let defered_responses = &mut VecDeque::new();
match tool_data.current_shape {
ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Arc | ShapeType::Polygon | ShapeType::Star => {
responses.add(GraphOperationMessage::TransformSet {
defered_responses.add(GraphOperationMessage::TransformSet {
layer,
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
transform_in: TransformIn::Viewport,
skip_rerender: false,
});
tool_options.fill.apply_fill(layer, responses);
tool_options.fill.apply_fill(layer, defered_responses);
}
ShapeType::Line => {
tool_data.line_data.weight = tool_options.line_weight;
tool_data.line_data.editing_layer = Some(layer);
}
}
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, defered_responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, defered_responses);
tool_data.data.layer = Some(layer);
responses.add(DeferMessage::AfterGraphRun {
messages: defered_responses.drain(..).collect(),
});
ShapeToolFsmState::Drawing(tool_data.current_shape)
}
(ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerMove(modifier)) => {

View file

@ -360,8 +360,6 @@ impl Fsm for SplineToolFsmState {
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
tool_data.current_layer = Some(layer);
responses.add(Message::StartBuffer);
SplineToolFsmState::Drawing
}
(SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => {

View file

@ -385,20 +385,25 @@ impl TextToolData {
parent: document.new_layer_parent(true),
insert_index: 0,
});
responses.add(Message::StartBuffer);
responses.add(GraphOperationMessage::FillSet {
layer: self.layer,
fill: if editing_text.color.is_some() {
Fill::Solid(editing_text.color.unwrap().to_gamma_srgb())
} else {
Fill::None
},
});
responses.add(GraphOperationMessage::TransformSet {
layer: self.layer,
transform: editing_text.transform,
transform_in: TransformIn::Viewport,
skip_rerender: true,
responses.add(DeferMessage::AfterGraphRun {
messages: vec![
GraphOperationMessage::FillSet {
layer: self.layer,
fill: if editing_text.color.is_some() {
Fill::Solid(editing_text.color.unwrap().to_gamma_srgb())
} else {
Fill::None
},
}
.into(),
GraphOperationMessage::TransformSet {
layer: self.layer,
transform: editing_text.transform,
transform_in: TransformIn::Viewport,
skip_rerender: true,
}
.into(),
],
});
self.editing_text = Some(editing_text);

View file

@ -3,7 +3,7 @@ use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::prelude::*;
use glam::{DAffine2, DVec2, UVec2};
use graph_craft::document::value::{RenderOutput, TaggedValue};
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, generate_uuid};
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences;
use graphene_std::application_io::TimingInformation;
@ -56,6 +56,7 @@ pub enum NodeGraphUpdate {
#[derive(Debug, Default)]
pub struct NodeGraphExecutor {
runtime_io: NodeRuntimeIO,
current_execution_id: u64,
futures: HashMap<u64, ExecutionContext>,
node_graph_hash: u64,
old_inspect_node: Option<NodeId>,
@ -78,13 +79,15 @@ impl NodeGraphExecutor {
futures: Default::default(),
runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
node_graph_hash: 0,
current_execution_id: 0,
old_inspect_node: None,
};
(node_runtime, node_executor)
}
/// Execute the network by flattening it and creating a borrow stack.
fn queue_execution(&self, render_config: RenderConfig) -> u64 {
let execution_id = generate_uuid();
fn queue_execution(&mut self, render_config: RenderConfig) -> u64 {
let execution_id = self.current_execution_id;
self.current_execution_id += 1;
let request = ExecutionRequest { execution_id, render_config };
self.runtime_io.send(GraphRuntimeRequest::ExecutionRequest(request)).expect("Failed to send generation request");
@ -105,7 +108,7 @@ impl NodeGraphExecutor {
#[cfg(test)]
pub(crate) fn update_node_graph_instrumented(&mut self, document: &mut DocumentMessageHandler) -> Result<Instrumented, String> {
// We should always invalidate the cache.
self.node_graph_hash = generate_uuid();
self.node_graph_hash = crate::application::generate_uuid();
let mut network = document.network_interface.document_network().clone();
let instrumented = Instrumented::new(&mut network);
@ -280,6 +283,7 @@ impl NodeGraphExecutor {
} else {
self.process_node_graph_output(node_graph_output, transform, responses)?
}
responses.add(DeferMessage::TriggerGraphRun(execution_id));
// Update the spreadsheet on the frontend using the value of the inspect result.
if self.old_inspect_node.is_some() {
@ -385,9 +389,22 @@ impl NodeGraphExecutor {
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
}
};
responses.add(Message::EndBuffer {
render_metadata: render_output_metadata,
let graphene_std::renderer::RenderMetadata {
upstream_footprints: footprints,
local_transforms,
first_instance_source_id,
click_targets,
clip_targets,
} = render_output_metadata;
// Run these update state messages immediately
responses.add(DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints: footprints,
local_transforms,
first_instance_source_id,
});
responses.add(DocumentMessage::UpdateClickTargets { click_targets });
responses.add(DocumentMessage::UpdateClipTargets { clip_targets });
responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw);

View file

@ -791,8 +791,6 @@ export class TriggerImport extends JsMessage {}
export class TriggerPaste extends JsMessage {}
export class TriggerDelayedZoomCanvasToFitAll extends JsMessage {}
export class TriggerDownloadImage extends JsMessage {
readonly svg!: string;
@ -1649,7 +1647,6 @@ export const messageMakers: Record<string, MessageMaker> = {
DisplayRemoveEditableTextbox,
SendUIMetadata,
TriggerAboutGraphiteLocalizedCommitDate,
TriggerDelayedZoomCanvasToFitAll,
TriggerDownloadImage,
TriggerDownloadTextFile,
TriggerFetchAndOpenDocument,

View file

@ -13,7 +13,6 @@ import {
UpdateWorkingColorsLayout,
UpdateNodeGraphControlBarLayout,
UpdateGraphViewOverlay,
TriggerDelayedZoomCanvasToFitAll,
UpdateGraphFadeArtwork,
} from "@graphite/messages";
@ -94,12 +93,6 @@ export function createDocumentState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(TriggerDelayedZoomCanvasToFitAll, () => {
// TODO: This is horribly hacky
[0, 1, 10, 50, 100, 200, 300, 400, 500].forEach((delay) => {
setTimeout(() => editor.handle.zoomCanvasToFitAll(), delay);
});
});
return {
subscribe,