Skip to content

Add handler for deferred execution of messages #2951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 8 additions & 41 deletions editor/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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"];

Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions editor/src/messages/defer/defer_message.rs
Original file line number Diff line number Diff line change
@@ -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> },
}
36 changes: 36 additions & 0 deletions editor/src/messages/defer/defer_message_handler.rs
Original file line number Diff line number Diff line change
@@ -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;
);
}
7 changes: 7 additions & 0 deletions editor/src/messages/defer/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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()],
});
}
}

Expand Down
1 change: 0 additions & 1 deletion editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ pub enum FrontendMessage {
#[serde(rename = "commitDate")]
commit_date: String,
},
TriggerDelayedZoomCanvasToFitAll,
TriggerDownloadImage {
svg: String,
name: String,
Expand Down
7 changes: 2 additions & 5 deletions editor/src/messages/message.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::messages::prelude::*;
use graphene_std::renderer::RenderMetadata;
use graphite_proc_macros::*;

#[impl_message]
Expand All @@ -15,6 +14,8 @@ pub enum Message {
#[child]
Debug(DebugMessage),
#[child]
Defer(DeferMessage),
#[child]
Dialog(DialogMessage),
#[child]
Frontend(FrontendMessage),
Expand All @@ -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`.
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 21 additions & 7 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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>) {
Expand Down
33 changes: 15 additions & 18 deletions editor/src/messages/portfolio/portfolio_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()],
});
}
}
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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 => {
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
5 changes: 3 additions & 2 deletions editor/src/messages/tool/tool_messages/brush_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
9 changes: 6 additions & 3 deletions editor/src/messages/tool/tool_messages/freehand_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions editor/src/messages/tool/tool_messages/pen_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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));
Expand Down
Loading
Loading