Skip to main content

jellyflow_runtime/runtime/store/subscriptions/
mod.rs

1//! Store subscription and selector internals.
2
3use crate::runtime::events::{
4    NodeGraphGestureEvent, NodeGraphStoreEvent, NodeGraphStoreSnapshot, SubscriptionToken,
5};
6
7use super::NodeGraphStore;
8use super::snapshot::StoreSnapshotParts;
9
10mod registry;
11mod selectors;
12
13pub(super) use self::registry::StoreSubscriptions;
14
15impl NodeGraphStore {
16    /// Subscribes to store events (graph commits + view-state changes).
17    ///
18    /// This is the minimal B-layer equivalent of XyFlow's store subscriptions.
19    pub fn subscribe(
20        &mut self,
21        f: impl for<'a> FnMut(NodeGraphStoreEvent<'a>) + 'static,
22    ) -> SubscriptionToken {
23        self.subscriptions.subscribe_event(f)
24    }
25
26    pub(crate) fn subscribe_gesture_with_token(
27        &mut self,
28        token: SubscriptionToken,
29        f: impl FnMut(NodeGraphGestureEvent) + 'static,
30    ) {
31        self.subscriptions.subscribe_gesture_with_token(token, f);
32    }
33
34    /// Subscribes to a derived projection of store state and only fires when the derived value
35    /// changes (by `PartialEq`).
36    ///
37    /// This is the B-layer "selector subscription" pattern used by XyFlow.
38    pub fn subscribe_selector<T>(
39        &mut self,
40        selector: impl for<'a> Fn(NodeGraphStoreSnapshot<'a>) -> T + 'static,
41        mut on_change: impl FnMut(&T) + 'static,
42    ) -> SubscriptionToken
43    where
44        T: PartialEq + 'static,
45    {
46        self.subscribe_selector_diff(selector, move |_prev, next| on_change(next))
47    }
48
49    /// Subscribes to a derived projection and receives both the previous and next values.
50    pub fn subscribe_selector_diff<T>(
51        &mut self,
52        selector: impl for<'a> Fn(NodeGraphStoreSnapshot<'a>) -> T + 'static,
53        on_change: impl FnMut(&T, &T) + 'static,
54    ) -> SubscriptionToken
55    where
56        T: PartialEq + 'static,
57    {
58        let token = self.subscriptions.allocate_token();
59        let initial = selector(self.snapshot());
60
61        self.subscriptions
62            .subscribe_selector_with_token(token, selector, initial, on_change);
63        token
64    }
65
66    /// Removes a subscription.
67    pub fn unsubscribe(&mut self, token: SubscriptionToken) -> bool {
68        self.subscriptions.unsubscribe(token)
69    }
70
71    pub(super) fn notify_selectors(&mut self) {
72        if !self.subscriptions.has_selectors() {
73            return;
74        }
75
76        let snapshot_parts = StoreSnapshotParts::from_store_fields(
77            &self.graph,
78            self.graph_revision,
79            self.layout_facts_revision,
80            &self.view_state,
81            &self.interaction,
82            &self.runtime_tuning,
83            &self.history,
84        );
85        self.subscriptions
86            .notify_selectors(snapshot_parts.snapshot());
87    }
88}