1use serde::{Deserialize, Serialize};
8
9use crate::runtime::connection::{
10 ConnectionHandleRef, ConnectionTargetCandidate, ResolvedConnectionTarget,
11};
12use crate::runtime::geometry::{EdgePosition, EdgeRouteFacts, HandleBounds, HandlePosition};
13use crate::runtime::lookups::NodeGraphLookups;
14use crate::runtime::rendering::RenderingQueryResult;
15use crate::runtime::store::NodeGraphStore;
16use crate::schema::NodeSurfaceSlotVisibility;
17use crate::schema::kit::NodeKitContentDensity;
18use jellyflow_core::core::{
19 CanvasPoint, CanvasRect, CanvasSize, EdgeId, Graph, NodeId, PortDirection, PortId, PortKey,
20};
21
22fn default_slot_visibility() -> NodeSurfaceSlotVisibility {
23 NodeSurfaceSlotVisibility::Visible
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
28pub struct MeasuredHandle {
29 pub handle: ConnectionHandleRef,
30 pub bounds: HandleBounds,
31}
32
33impl MeasuredHandle {
34 pub fn new(handle: ConnectionHandleRef, bounds: HandleBounds) -> Self {
35 Self { handle, bounds }
36 }
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub struct MeasuredSurfaceSlot {
42 pub key: String,
43 pub rect: CanvasRect,
44 #[serde(default = "default_slot_visibility")]
45 pub visibility: NodeSurfaceSlotVisibility,
46}
47
48impl MeasuredSurfaceSlot {
49 pub fn new(key: impl Into<String>, rect: CanvasRect) -> Self {
50 Self {
51 key: key.into(),
52 rect,
53 visibility: NodeSurfaceSlotVisibility::Visible,
54 }
55 }
56
57 pub fn with_visibility(mut self, visibility: NodeSurfaceSlotVisibility) -> Self {
58 self.visibility = visibility;
59 self
60 }
61
62 pub fn is_visible(&self) -> bool {
63 matches!(self.visibility, NodeSurfaceSlotVisibility::Visible)
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub struct MeasuredSurfaceAnchor {
70 pub anchor: String,
71 pub rect: CanvasRect,
72 pub position: HandlePosition,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub port: Option<PortId>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub port_key: Option<PortKey>,
77 #[serde(default = "default_slot_visibility")]
78 pub visibility: NodeSurfaceSlotVisibility,
79}
80
81impl MeasuredSurfaceAnchor {
82 pub fn new(anchor: impl Into<String>, rect: CanvasRect, position: HandlePosition) -> Self {
83 Self {
84 anchor: anchor.into(),
85 rect,
86 position,
87 port: None,
88 port_key: None,
89 visibility: NodeSurfaceSlotVisibility::Visible,
90 }
91 }
92
93 pub fn with_port(mut self, port: PortId) -> Self {
94 self.port = Some(port);
95 self
96 }
97
98 pub fn with_port_key(mut self, port_key: impl Into<PortKey>) -> Self {
99 self.port_key = Some(port_key.into());
100 self
101 }
102
103 pub fn with_visibility(mut self, visibility: NodeSurfaceSlotVisibility) -> Self {
104 self.visibility = visibility;
105 self
106 }
107
108 pub fn is_visible(&self) -> bool {
109 matches!(self.visibility, NodeSurfaceSlotVisibility::Visible)
110 }
111
112 pub fn bounds(&self) -> HandleBounds {
113 HandleBounds {
114 rect: self.rect,
115 position: self.position,
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122pub struct NodeMeasurement {
123 pub node: NodeId,
124 #[serde(default)]
125 pub revision: u64,
126 #[serde(default, skip_serializing_if = "Option::is_none")]
127 pub density: Option<NodeKitContentDensity>,
128 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub size: Option<CanvasSize>,
130 #[serde(default, skip_serializing_if = "Vec::is_empty")]
131 pub handles: Vec<MeasuredHandle>,
132 #[serde(default, skip_serializing_if = "Vec::is_empty")]
133 pub slots: Vec<MeasuredSurfaceSlot>,
134 #[serde(default, skip_serializing_if = "Vec::is_empty")]
135 pub anchors: Vec<MeasuredSurfaceAnchor>,
136}
137
138impl NodeMeasurement {
139 pub fn new(node: NodeId) -> Self {
140 Self {
141 node,
142 revision: 0,
143 density: None,
144 size: None,
145 handles: Vec::new(),
146 slots: Vec::new(),
147 anchors: Vec::new(),
148 }
149 }
150
151 pub fn with_revision(mut self, revision: u64) -> Self {
152 self.revision = revision;
153 self
154 }
155
156 pub fn with_density(mut self, density: Option<NodeKitContentDensity>) -> Self {
157 self.density = density;
158 self
159 }
160
161 pub fn with_size(mut self, size: Option<CanvasSize>) -> Self {
162 self.size = size;
163 self
164 }
165
166 pub fn with_handles(mut self, handles: impl IntoIterator<Item = MeasuredHandle>) -> Self {
167 self.handles = handles.into_iter().collect();
168 self
169 }
170
171 pub fn with_slots(mut self, slots: impl IntoIterator<Item = MeasuredSurfaceSlot>) -> Self {
172 self.slots = slots.into_iter().collect();
173 self
174 }
175
176 pub fn with_anchors(
177 mut self,
178 anchors: impl IntoIterator<Item = MeasuredSurfaceAnchor>,
179 ) -> Self {
180 self.anchors = anchors.into_iter().collect();
181 self
182 }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
187#[serde(rename_all = "snake_case")]
188pub enum NodeInternalsInvalidationReason {
189 DataChanged,
190 ComponentStateChanged,
191 ZoomChanged,
192 SizeChanged,
193 DensityChanged,
194 AdapterRequest,
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
199pub struct NodeInternalsInvalidation {
200 pub nodes: Vec<NodeId>,
201 pub reason: NodeInternalsInvalidationReason,
202}
203
204impl NodeInternalsInvalidation {
205 pub fn new(
206 nodes: impl IntoIterator<Item = NodeId>,
207 reason: NodeInternalsInvalidationReason,
208 ) -> Self {
209 Self {
210 nodes: nodes.into_iter().collect(),
211 reason,
212 }
213 }
214
215 pub fn one(node: NodeId, reason: NodeInternalsInvalidationReason) -> Self {
216 Self::new([node], reason)
217 }
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
222#[serde(rename_all = "snake_case", tag = "state")]
223pub enum NodeMeasurementStatus {
224 Missing,
225 Fresh {
226 revision: u64,
227 },
228 Dirty {
229 revision: u64,
230 reason: NodeInternalsInvalidationReason,
231 },
232}
233
234impl NodeMeasurementStatus {
235 pub fn is_fresh(self) -> bool {
236 matches!(self, Self::Fresh { .. })
237 }
238
239 pub fn is_dirty(self) -> bool {
240 matches!(self, Self::Dirty { .. })
241 }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
246#[serde(rename_all = "snake_case")]
247pub enum NodeHandleFallbackReason {
248 MissingMeasurement,
249 DirtyMeasurement,
250 MissingHandle,
251}
252
253#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
255#[serde(rename_all = "snake_case", tag = "source")]
256pub enum NodeHandleMeasurementSource {
257 MeasuredHandle,
258 MeasuredAnchor { anchor: String },
259 Fallback { reason: NodeHandleFallbackReason },
260}
261
262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
264pub struct NodeHandleMeasurementResolution {
265 pub handle: ConnectionHandleRef,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
267 pub bounds: Option<HandleBounds>,
268 pub source: NodeHandleMeasurementSource,
269 pub status: NodeMeasurementStatus,
270}
271
272#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274pub enum NodeMeasurementOutcome {
275 Changed,
276 Unchanged,
277}
278
279impl NodeMeasurementOutcome {
280 pub fn changed(self) -> bool {
281 matches!(self, Self::Changed)
282 }
283}
284
285pub struct NodeInternalsController<'a> {
291 store: &'a mut NodeGraphStore,
292}
293
294impl<'a> NodeInternalsController<'a> {
295 pub(crate) fn new(store: &'a mut NodeGraphStore) -> Self {
296 Self { store }
297 }
298
299 pub fn invalidate(
301 &mut self,
302 invalidation: NodeInternalsInvalidation,
303 ) -> NodeMeasurementOutcome {
304 self.store.invalidate_node_internals(invalidation)
305 }
306
307 pub fn invalidate_one(
309 &mut self,
310 node: NodeId,
311 reason: NodeInternalsInvalidationReason,
312 ) -> NodeMeasurementOutcome {
313 self.invalidate(NodeInternalsInvalidation::one(node, reason))
314 }
315
316 pub fn report(
318 &mut self,
319 measurement: NodeMeasurement,
320 ) -> Result<NodeMeasurementOutcome, NodeMeasurementError> {
321 self.store.report_node_measurement(measurement)
322 }
323
324 pub fn status(&self, node: NodeId) -> NodeMeasurementStatus {
326 self.store.node_measurement_status(node)
327 }
328
329 pub fn resolve_handle(&self, handle: ConnectionHandleRef) -> NodeHandleMeasurementResolution {
331 self.store.resolve_node_handle_measurement(handle)
332 }
333
334 pub fn layout_facts(&self, viewport_size: CanvasSize) -> LayoutFactsQueryResult {
336 self.store.layout_facts_query(viewport_size)
337 }
338}
339
340#[derive(Debug, thiserror::Error)]
341pub enum NodeMeasurementError {
342 #[error("measurement target node does not exist: {0:?}")]
343 MissingNode(NodeId),
344 #[error("measurement size is not positive and finite for node {node:?}: {size:?}")]
345 InvalidSize { node: NodeId, size: CanvasSize },
346 #[error("measurement handle does not belong to node {node:?}: {handle:?}")]
347 InvalidHandle {
348 node: NodeId,
349 handle: ConnectionHandleRef,
350 },
351 #[error("measurement handle bounds are not positive and finite for node {node:?}: {handle:?}")]
352 InvalidHandleBounds {
353 node: NodeId,
354 handle: ConnectionHandleRef,
355 },
356 #[error("measurement slot rect is not positive and finite for node {node:?}: {slot}")]
357 InvalidSlotRect { node: NodeId, slot: String },
358 #[error("measurement anchor rect is not positive and finite for node {node:?}: {anchor}")]
359 InvalidAnchorRect { node: NodeId, anchor: String },
360 #[error("measurement anchor target does not belong to node {node:?}: {anchor}")]
361 InvalidAnchorTarget { node: NodeId, anchor: String },
362}
363
364#[derive(Debug, Clone, Copy, PartialEq)]
366pub struct LayoutEdgePosition {
367 pub edge: EdgeId,
368 pub position: EdgePosition,
369}
370
371impl LayoutEdgePosition {
372 pub fn new(edge: EdgeId, position: EdgePosition) -> Self {
373 Self { edge, position }
374 }
375}
376
377#[derive(Debug, Clone, PartialEq)]
379pub struct LayoutEdgeRouteFacts {
380 pub edge: EdgeId,
381 pub facts: EdgeRouteFacts,
382}
383
384impl LayoutEdgeRouteFacts {
385 pub fn new(edge: EdgeId, facts: EdgeRouteFacts) -> Self {
386 Self { edge, facts }
387 }
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
392pub struct LayoutNodeMeasurementStatus {
393 pub node: NodeId,
394 pub status: NodeMeasurementStatus,
395}
396
397impl LayoutNodeMeasurementStatus {
398 pub fn new(node: NodeId, status: NodeMeasurementStatus) -> Self {
399 Self { node, status }
400 }
401}
402
403#[derive(Debug, Clone, PartialEq)]
405pub struct LayoutFactsQueryResult {
406 pub revision: u64,
407 pub rendering: RenderingQueryResult,
408 pub visible_edge_positions: Vec<LayoutEdgePosition>,
409 pub visible_edge_route_facts: Vec<LayoutEdgeRouteFacts>,
410 pub connection_target_candidates: Vec<ConnectionTargetCandidate>,
411 pub node_measurement_statuses: Vec<LayoutNodeMeasurementStatus>,
412}
413
414impl LayoutFactsQueryResult {
415 pub fn new(
416 revision: u64,
417 rendering: RenderingQueryResult,
418 visible_edge_positions: Vec<LayoutEdgePosition>,
419 connection_target_candidates: Vec<ConnectionTargetCandidate>,
420 ) -> Self {
421 Self {
422 revision,
423 rendering,
424 visible_edge_positions,
425 visible_edge_route_facts: Vec::new(),
426 connection_target_candidates,
427 node_measurement_statuses: Vec::new(),
428 }
429 }
430
431 pub fn with_edge_route_facts(
432 mut self,
433 facts: impl IntoIterator<Item = LayoutEdgeRouteFacts>,
434 ) -> Self {
435 self.visible_edge_route_facts = facts.into_iter().collect();
436 self
437 }
438
439 pub fn with_node_measurement_statuses(
440 mut self,
441 statuses: impl IntoIterator<Item = LayoutNodeMeasurementStatus>,
442 ) -> Self {
443 self.node_measurement_statuses = statuses.into_iter().collect();
444 self
445 }
446
447 pub fn visible_edge_position(&self, edge: EdgeId) -> Option<EdgePosition> {
448 self.visible_edge_positions
449 .iter()
450 .find(|position| position.edge == edge)
451 .map(|position| position.position)
452 }
453
454 pub fn visible_edge_route_facts(&self, edge: EdgeId) -> Option<&EdgeRouteFacts> {
455 self.visible_edge_route_facts
456 .iter()
457 .find(|facts| facts.edge == edge)
458 .map(|facts| &facts.facts)
459 }
460
461 pub fn node_measurement_status(&self, node: NodeId) -> NodeMeasurementStatus {
462 self.node_measurement_statuses
463 .iter()
464 .find(|status| status.node == node)
465 .map(|status| status.status)
466 .unwrap_or(NodeMeasurementStatus::Missing)
467 }
468}
469
470impl NodeGraphStore {
471 pub fn node_internals(&mut self) -> NodeInternalsController<'_> {
473 NodeInternalsController::new(self)
474 }
475
476 pub fn report_node_measurement(
478 &mut self,
479 measurement: NodeMeasurement,
480 ) -> Result<NodeMeasurementOutcome, NodeMeasurementError> {
481 let measurement = self.validate_node_measurement(measurement)?;
482 let Some(entry) = self.lookups_mut().node_lookup.get_mut(&measurement.node) else {
483 return Err(NodeMeasurementError::MissingNode(measurement.node));
484 };
485
486 if entry.apply_measurement(&measurement) {
487 self.publish_layout_facts_changed();
488 Ok(NodeMeasurementOutcome::Changed)
489 } else {
490 Ok(NodeMeasurementOutcome::Unchanged)
491 }
492 }
493
494 pub fn clear_node_measurement(&mut self, node: NodeId) -> NodeMeasurementOutcome {
496 let Some(entry) = self.lookups_mut().node_lookup.get_mut(&node) else {
497 return NodeMeasurementOutcome::Unchanged;
498 };
499
500 if entry.clear_measurement() {
501 self.publish_layout_facts_changed();
502 NodeMeasurementOutcome::Changed
503 } else {
504 NodeMeasurementOutcome::Unchanged
505 }
506 }
507
508 pub fn invalidate_node_internals(
513 &mut self,
514 invalidation: NodeInternalsInvalidation,
515 ) -> NodeMeasurementOutcome {
516 let mut changed = false;
517 for node in invalidation.nodes {
518 let Some(entry) = self.lookups_mut().node_lookup.get_mut(&node) else {
519 continue;
520 };
521 changed |= entry.mark_measurement_dirty(invalidation.reason);
522 }
523
524 if changed {
525 self.publish_layout_facts_changed();
526 NodeMeasurementOutcome::Changed
527 } else {
528 NodeMeasurementOutcome::Unchanged
529 }
530 }
531
532 pub fn node_measurement(&self, node: NodeId) -> Option<NodeMeasurement> {
534 self.lookups()
535 .node_lookup
536 .get(&node)
537 .and_then(|entry| entry.measurement(node))
538 }
539
540 pub fn node_measurement_status(&self, node: NodeId) -> NodeMeasurementStatus {
542 self.lookups()
543 .node_lookup
544 .get(&node)
545 .map(|entry| entry.measurement_status())
546 .unwrap_or(NodeMeasurementStatus::Missing)
547 }
548
549 pub fn resolve_node_handle_measurement(
551 &self,
552 handle: ConnectionHandleRef,
553 ) -> NodeHandleMeasurementResolution {
554 resolve_handle_measurement(self.graph(), self.lookups(), handle)
555 }
556
557 pub fn layout_facts_query(&self, viewport_size: CanvasSize) -> LayoutFactsQueryResult {
559 crate::runtime::query::layout_facts_query(self, viewport_size)
560 }
561
562 pub fn connection_target_candidates_from_layout_facts(&self) -> Vec<ConnectionTargetCandidate> {
564 crate::runtime::query::connection_target_candidates_from_layout_facts(self)
565 }
566
567 pub fn resolve_connection_target_from_layout_facts(
569 &self,
570 pointer: CanvasPoint,
571 from: ConnectionHandleRef,
572 ) -> ResolvedConnectionTarget {
573 crate::runtime::query::resolve_connection_target_from_layout_facts(self, pointer, from)
574 }
575
576 pub fn edge_position_from_layout_facts(&self, edge: EdgeId) -> Option<EdgePosition> {
578 crate::runtime::query::edge_position_from_layout_facts(self, edge)
579 }
580
581 fn validate_node_measurement(
582 &self,
583 measurement: NodeMeasurement,
584 ) -> Result<NodeMeasurement, NodeMeasurementError> {
585 if !self.graph().nodes().contains_key(&measurement.node) {
586 return Err(NodeMeasurementError::MissingNode(measurement.node));
587 }
588 if let Some(size) = measurement.size
589 && !size.is_positive_finite()
590 {
591 return Err(NodeMeasurementError::InvalidSize {
592 node: measurement.node,
593 size,
594 });
595 }
596
597 for measured in &measurement.handles {
598 if measured.handle.node != measurement.node {
599 return Err(NodeMeasurementError::InvalidHandle {
600 node: measurement.node,
601 handle: measured.handle,
602 });
603 }
604 if !measured.bounds.rect.is_positive_finite() {
605 return Err(NodeMeasurementError::InvalidHandleBounds {
606 node: measurement.node,
607 handle: measured.handle,
608 });
609 }
610 let Some(port) = self.graph().ports().get(&measured.handle.port) else {
611 return Err(NodeMeasurementError::InvalidHandle {
612 node: measurement.node,
613 handle: measured.handle,
614 });
615 };
616 if port.node != measurement.node || port.dir != measured.handle.direction {
617 return Err(NodeMeasurementError::InvalidHandle {
618 node: measurement.node,
619 handle: measured.handle,
620 });
621 }
622 }
623
624 for slot in &measurement.slots {
625 if !slot.rect.is_positive_finite() {
626 return Err(NodeMeasurementError::InvalidSlotRect {
627 node: measurement.node,
628 slot: slot.key.clone(),
629 });
630 }
631 }
632
633 for anchor in &measurement.anchors {
634 if !anchor.rect.is_positive_finite() {
635 return Err(NodeMeasurementError::InvalidAnchorRect {
636 node: measurement.node,
637 anchor: anchor.anchor.clone(),
638 });
639 }
640 let Some(port) = anchor
641 .port
642 .and_then(|port| self.graph().ports().get(&port).map(|model| (port, model)))
643 else {
644 if anchor.port.is_some() {
645 return Err(NodeMeasurementError::InvalidAnchorTarget {
646 node: measurement.node,
647 anchor: anchor.anchor.clone(),
648 });
649 }
650 continue;
651 };
652 if port.1.node != measurement.node {
653 return Err(NodeMeasurementError::InvalidAnchorTarget {
654 node: measurement.node,
655 anchor: anchor.anchor.clone(),
656 });
657 }
658 if let Some(port_key) = &anchor.port_key
659 && port_key != &port.1.key
660 {
661 return Err(NodeMeasurementError::InvalidAnchorTarget {
662 node: measurement.node,
663 anchor: anchor.anchor.clone(),
664 });
665 }
666 if !anchor_position_matches_direction(anchor.position, port.1.dir) {
667 return Err(NodeMeasurementError::InvalidAnchorTarget {
668 node: measurement.node,
669 anchor: anchor.anchor.clone(),
670 });
671 }
672 }
673
674 Ok(measurement)
675 }
676}
677
678pub(crate) fn resolve_handle_measurement(
679 graph: &Graph,
680 lookups: &NodeGraphLookups,
681 handle: ConnectionHandleRef,
682) -> NodeHandleMeasurementResolution {
683 let Some(entry) = lookups.node_lookup.get(&handle.node) else {
684 return NodeHandleMeasurementResolution {
685 handle,
686 bounds: None,
687 source: NodeHandleMeasurementSource::Fallback {
688 reason: NodeHandleFallbackReason::MissingMeasurement,
689 },
690 status: NodeMeasurementStatus::Missing,
691 };
692 };
693 let status = entry.measurement_status();
694 if !status.is_fresh() {
695 let reason = match status {
696 NodeMeasurementStatus::Missing => NodeHandleFallbackReason::MissingMeasurement,
697 NodeMeasurementStatus::Dirty { .. } => NodeHandleFallbackReason::DirtyMeasurement,
698 NodeMeasurementStatus::Fresh { .. } => NodeHandleFallbackReason::MissingHandle,
699 };
700 return NodeHandleMeasurementResolution {
701 handle,
702 bounds: None,
703 source: NodeHandleMeasurementSource::Fallback { reason },
704 status,
705 };
706 }
707 if let Some(measured) = entry
708 .measured_handles
709 .iter()
710 .find(|measured| measured.handle == handle)
711 {
712 return NodeHandleMeasurementResolution {
713 handle,
714 bounds: Some(measured.bounds),
715 source: NodeHandleMeasurementSource::MeasuredHandle,
716 status,
717 };
718 }
719
720 let anchor = graph.ports().get(&handle.port).and_then(|port| {
721 if port.node != handle.node || port.dir != handle.direction {
722 return None;
723 }
724 entry.measured_anchors.iter().find(|anchor| {
725 anchor.is_visible()
726 && (anchor.port == Some(handle.port) || anchor.port_key.as_ref() == Some(&port.key))
727 && anchor_position_matches_direction(anchor.position, port.dir)
728 })
729 });
730 if let Some(anchor) = anchor {
731 return NodeHandleMeasurementResolution {
732 handle,
733 bounds: Some(anchor.bounds()),
734 source: NodeHandleMeasurementSource::MeasuredAnchor {
735 anchor: anchor.anchor.clone(),
736 },
737 status,
738 };
739 }
740
741 NodeHandleMeasurementResolution {
742 handle,
743 bounds: None,
744 source: NodeHandleMeasurementSource::Fallback {
745 reason: NodeHandleFallbackReason::MissingHandle,
746 },
747 status,
748 }
749}
750
751pub(crate) fn anchor_position_matches_direction(
752 position: HandlePosition,
753 direction: PortDirection,
754) -> bool {
755 match direction {
756 PortDirection::In => matches!(position, HandlePosition::Left | HandlePosition::Top),
757 PortDirection::Out => matches!(position, HandlePosition::Right | HandlePosition::Bottom),
758 }
759}