jellyflow_runtime/runtime/
measurement.rs1use serde::{Deserialize, Serialize};
8
9use crate::runtime::connection::{
10 ConnectionHandleRef, ConnectionTargetCandidate, ResolvedConnectionTarget,
11};
12use crate::runtime::geometry::{EdgePosition, HandleBounds};
13use crate::runtime::rendering::RenderingQueryResult;
14use crate::runtime::store::NodeGraphStore;
15use jellyflow_core::core::{CanvasPoint, CanvasSize, EdgeId, NodeId};
16
17#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
19pub struct MeasuredHandle {
20 pub handle: ConnectionHandleRef,
21 pub bounds: HandleBounds,
22}
23
24impl MeasuredHandle {
25 pub fn new(handle: ConnectionHandleRef, bounds: HandleBounds) -> Self {
26 Self { handle, bounds }
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct NodeMeasurement {
33 pub node: NodeId,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub size: Option<CanvasSize>,
36 #[serde(default, skip_serializing_if = "Vec::is_empty")]
37 pub handles: Vec<MeasuredHandle>,
38}
39
40impl NodeMeasurement {
41 pub fn new(node: NodeId) -> Self {
42 Self {
43 node,
44 size: None,
45 handles: Vec::new(),
46 }
47 }
48
49 pub fn with_size(mut self, size: Option<CanvasSize>) -> Self {
50 self.size = size;
51 self
52 }
53
54 pub fn with_handles(mut self, handles: impl IntoIterator<Item = MeasuredHandle>) -> Self {
55 self.handles = handles.into_iter().collect();
56 self
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum NodeMeasurementOutcome {
63 Changed,
64 Unchanged,
65}
66
67impl NodeMeasurementOutcome {
68 pub fn changed(self) -> bool {
69 matches!(self, Self::Changed)
70 }
71}
72
73#[derive(Debug, thiserror::Error)]
74pub enum NodeMeasurementError {
75 #[error("measurement target node does not exist: {0:?}")]
76 MissingNode(NodeId),
77 #[error("measurement size is not positive and finite for node {node:?}: {size:?}")]
78 InvalidSize { node: NodeId, size: CanvasSize },
79 #[error("measurement handle does not belong to node {node:?}: {handle:?}")]
80 InvalidHandle {
81 node: NodeId,
82 handle: ConnectionHandleRef,
83 },
84 #[error("measurement handle bounds are not positive and finite for node {node:?}: {handle:?}")]
85 InvalidHandleBounds {
86 node: NodeId,
87 handle: ConnectionHandleRef,
88 },
89}
90
91#[derive(Debug, Clone, Copy, PartialEq)]
93pub struct LayoutEdgePosition {
94 pub edge: EdgeId,
95 pub position: EdgePosition,
96}
97
98impl LayoutEdgePosition {
99 pub fn new(edge: EdgeId, position: EdgePosition) -> Self {
100 Self { edge, position }
101 }
102}
103
104#[derive(Debug, Clone, PartialEq)]
106pub struct LayoutFactsQueryResult {
107 pub revision: u64,
108 pub rendering: RenderingQueryResult,
109 pub visible_edge_positions: Vec<LayoutEdgePosition>,
110 pub connection_target_candidates: Vec<ConnectionTargetCandidate>,
111}
112
113impl LayoutFactsQueryResult {
114 pub fn new(
115 revision: u64,
116 rendering: RenderingQueryResult,
117 visible_edge_positions: Vec<LayoutEdgePosition>,
118 connection_target_candidates: Vec<ConnectionTargetCandidate>,
119 ) -> Self {
120 Self {
121 revision,
122 rendering,
123 visible_edge_positions,
124 connection_target_candidates,
125 }
126 }
127
128 pub fn visible_edge_position(&self, edge: EdgeId) -> Option<EdgePosition> {
129 self.visible_edge_positions
130 .iter()
131 .find(|position| position.edge == edge)
132 .map(|position| position.position)
133 }
134}
135
136impl NodeGraphStore {
137 pub fn report_node_measurement(
139 &mut self,
140 measurement: NodeMeasurement,
141 ) -> Result<NodeMeasurementOutcome, NodeMeasurementError> {
142 let measurement = self.validate_node_measurement(measurement)?;
143 let Some(entry) = self.lookups_mut().node_lookup.get_mut(&measurement.node) else {
144 return Err(NodeMeasurementError::MissingNode(measurement.node));
145 };
146
147 if entry.apply_measurement(&measurement) {
148 self.publish_layout_facts_changed();
149 Ok(NodeMeasurementOutcome::Changed)
150 } else {
151 Ok(NodeMeasurementOutcome::Unchanged)
152 }
153 }
154
155 pub fn clear_node_measurement(&mut self, node: NodeId) -> NodeMeasurementOutcome {
157 let Some(entry) = self.lookups_mut().node_lookup.get_mut(&node) else {
158 return NodeMeasurementOutcome::Unchanged;
159 };
160
161 if entry.clear_measurement() {
162 self.publish_layout_facts_changed();
163 NodeMeasurementOutcome::Changed
164 } else {
165 NodeMeasurementOutcome::Unchanged
166 }
167 }
168
169 pub fn node_measurement(&self, node: NodeId) -> Option<NodeMeasurement> {
171 self.lookups()
172 .node_lookup
173 .get(&node)
174 .and_then(|entry| entry.measurement(node))
175 }
176
177 pub fn layout_facts_query(&self, viewport_size: CanvasSize) -> LayoutFactsQueryResult {
179 crate::runtime::query::layout_facts_query(self, viewport_size)
180 }
181
182 pub fn connection_target_candidates_from_layout_facts(&self) -> Vec<ConnectionTargetCandidate> {
184 crate::runtime::query::connection_target_candidates_from_layout_facts(self)
185 }
186
187 pub fn resolve_connection_target_from_layout_facts(
189 &self,
190 pointer: CanvasPoint,
191 from: ConnectionHandleRef,
192 ) -> ResolvedConnectionTarget {
193 crate::runtime::query::resolve_connection_target_from_layout_facts(self, pointer, from)
194 }
195
196 pub fn edge_position_from_layout_facts(&self, edge: EdgeId) -> Option<EdgePosition> {
198 crate::runtime::query::edge_position_from_layout_facts(self, edge)
199 }
200
201 fn validate_node_measurement(
202 &self,
203 measurement: NodeMeasurement,
204 ) -> Result<NodeMeasurement, NodeMeasurementError> {
205 if !self.graph().nodes.contains_key(&measurement.node) {
206 return Err(NodeMeasurementError::MissingNode(measurement.node));
207 }
208 if let Some(size) = measurement.size
209 && !size.is_positive_finite()
210 {
211 return Err(NodeMeasurementError::InvalidSize {
212 node: measurement.node,
213 size,
214 });
215 }
216
217 for measured in &measurement.handles {
218 if measured.handle.node != measurement.node {
219 return Err(NodeMeasurementError::InvalidHandle {
220 node: measurement.node,
221 handle: measured.handle,
222 });
223 }
224 if !measured.bounds.rect.is_positive_finite() {
225 return Err(NodeMeasurementError::InvalidHandleBounds {
226 node: measurement.node,
227 handle: measured.handle,
228 });
229 }
230 let Some(port) = self.graph().ports.get(&measured.handle.port) else {
231 return Err(NodeMeasurementError::InvalidHandle {
232 node: measurement.node,
233 handle: measured.handle,
234 });
235 };
236 if port.node != measurement.node || port.dir != measured.handle.direction {
237 return Err(NodeMeasurementError::InvalidHandle {
238 node: measurement.node,
239 handle: measured.handle,
240 });
241 }
242 }
243
244 Ok(measurement)
245 }
246}