dittolive_ditto/store/
observer.rs

1use_prelude!();
2
3use std::hash::{self, Hash};
4
5use ffi_sdk::{
6    ffi_utils::repr_c, FfiQueryResult, FfiSignalNext, FfiStoreObservationHandlerWithSignalNext,
7    FfiStoreObserver,
8};
9use uuid::Uuid;
10
11pub use super::*;
12use crate::{error::DittoError, utils::zstr::zstr};
13
14/// Use [`ditto.store().register_observer(...)`] to create an observer with a callback.
15///
16/// [`ditto.store().register_observer(...)`]: crate::store::Store::register_observer
17pub struct StoreObserver {
18    pub(crate) handle: repr_c::Box<FfiStoreObserver>,
19}
20
21trait_alias! {
22    /// A change handler is called whenever an active store observer receives new results.
23    pub trait ChangeHandler =
24        FnMut(QueryResult)
25        + Send // can be dropped in another thread
26        + Sync
27        + 'static // cannot dangle
28}
29
30trait_alias! {
31    /// A callback that tells Ditto the observer's handler is ready to receive another callback.
32    ///
33    /// This is a mechanism for handling backpressure when updates arrive faster than the handler
34    /// can process them. Until this callback is invoked, Ditto pauses further callbacks while
35    /// continuing to apply changes to the local store. If multiple relevant changes occur while
36    /// callbacks are paused, Ditto does not queue a callback for each change. After this callback
37    /// is invoked, Ditto calls the handler at most once with the latest result, and only if that
38    /// result differs from the last result delivered.
39    pub trait SignalNext =
40        FnOnce()
41        + 'static // cannot dangle
42        + Send // can be dropped in another thread
43}
44
45trait_alias! {
46    /// A change handler is called whenever an active store observer receives new results.
47    ///
48    /// Call the provided [`SignalNext`] function to signal that the handler is ready to
49    /// receive the next callback from the store observer. See [`SignalNext`] for how Ditto
50    /// handles updates while waiting for this signal.
51    pub trait ChangeHandlerWithSignalNext =
52        FnMut(QueryResult, Box<dyn SignalNext>)
53        + 'static // cannot dangle
54        + Send // can be dropped in another thread
55        + Sync
56}
57
58fn dittoffi_store_observer_register_safe<F>(
59    ditto: &'_ Ditto,
60    query: &zstr,
61    query_args: Option<&[u8]>,
62    mut observer: F,
63) -> Result<StoreObserver>
64where
65    F: ChangeHandlerWithSignalNext,
66{
67    let ffi_callback: FfiStoreObservationHandlerWithSignalNext = {
68        fn make_callback<F>(f: F) -> FfiStoreObservationHandlerWithSignalNext
69        where
70            F: FnMut(repr_c::Box<FfiQueryResult>, repr_c::Arc<FfiSignalNext>) + 'static + Send,
71        {
72            FfiStoreObservationHandlerWithSignalNext(Box::new(f).into())
73        }
74
75        make_callback(
76            move |ffi_query_result: repr_c::Box<FfiQueryResult>,
77                  signal_next: repr_c::Arc<FfiSignalNext>| {
78                let signal_next = Box::new(move || {
79                    signal_next.call();
80                });
81
82                observer(QueryResult::from(ffi_query_result), signal_next);
83            },
84        )
85    };
86
87    let query = query.into();
88    let query_args_cbor = query_args.map(|qa| qa.into());
89
90    let handle = ffi_sdk::dittoffi_store_register_observer_throws(
91        &ditto.ditto,
92        query,
93        query_args_cbor,
94        ffi_callback,
95    )
96    .into_rust_result()?;
97
98    Ok(StoreObserver { handle })
99}
100
101impl StoreObserver {
102    pub(crate) fn new<F>(
103        ditto: &Ditto,
104        query: &zstr,
105        query_args: Option<&[u8]>,
106        mut on_change: F,
107    ) -> Result<Self, DittoError>
108    where
109        F: ChangeHandler,
110    {
111        Self::with_signal_next(ditto, query, query_args, move |args, signal_next| {
112            on_change(args);
113            signal_next();
114        })
115    }
116
117    pub(crate) fn with_signal_next<F>(
118        ditto: &Ditto,
119        query: &zstr,
120        query_args: Option<&[u8]>,
121        on_change: F,
122    ) -> Result<Self, DittoError>
123    where
124        F: ChangeHandlerWithSignalNext,
125    {
126        let on_change = {
127            let on_change = ::std::sync::Mutex::new(on_change);
128            move |arg: QueryResult, signal_next: Box<dyn SignalNext>| {
129                let mut on_change = on_change
130                    .lock()
131                    .expect("`on_change` observer not to be poisoned");
132                on_change(arg, signal_next)
133            }
134        };
135        dittoffi_store_observer_register_safe(ditto, query, query_args, on_change)
136    }
137
138    /// Return the query string used to create this `StoreObserver`.
139    pub fn query_string(&self) -> String {
140        let char_p = ffi_sdk::dittoffi_store_observer_query_string(&self.handle);
141        char_p.into_string()
142    }
143
144    /// Return the query arguments used to create this `StoreObserver`, if any.
145    pub fn query_arguments(&self) -> Option<serde_cbor::Value> {
146        let buffer: c_slice::Box<u8> =
147            ffi_sdk::dittoffi_store_observer_query_arguments_cbor(&self.handle)?;
148        let cbor = serde_cbor::from_slice(buffer.as_slice())
149            .unwrap_or_else(|error| panic!("bug: failed to deserialize CBOR from FFI: {error}"));
150
151        Some(cbor)
152    }
153
154    /// Cancels this [`StoreObserver`] and its callback.
155    ///
156    /// The callback registered with this [`StoreObserver`] will no longer be called
157    /// after calling [`.cancel()`].
158    ///
159    /// This call is a no-op if this [`StoreObserver`] has already been cancelled
160    /// or if the owning [`Ditto`] object has gone out of scope.
161    ///
162    /// [`.cancel()`]: Self::cancel
163    pub fn cancel(&self) {
164        ffi_sdk::dittoffi_store_observer_cancel(&self.handle);
165    }
166
167    /// Returns true if this [`StoreObserver`] has been cancelled.
168    pub fn is_cancelled(&self) -> bool {
169        ffi_sdk::dittoffi_store_observer_is_cancelled(&self.handle)
170    }
171
172    /// Returns the unique identifier for this [`StoreObserver`].
173    fn id(&self) -> Uuid {
174        let buffer = ffi_sdk::dittoffi_store_observer_id(&self.handle);
175        Uuid::from_slice(buffer.as_slice()).expect("bug: expected valid UUID")
176    }
177}
178
179impl StoreObserver {
180    fn comparable_parts(&self) -> impl '_ + Eq + Hash {
181        self.id()
182    }
183}
184
185impl Eq for StoreObserver {}
186impl PartialEq for StoreObserver {
187    fn eq(&self, other: &Self) -> bool {
188        self.comparable_parts() == other.comparable_parts()
189    }
190}
191
192impl Hash for StoreObserver {
193    fn hash<H: hash::Hasher>(&self, h: &mut H) {
194        self.comparable_parts().hash(h)
195    }
196}