Skip to main content

morphix/builtin/
snapshot.rs

1use std::marker::PhantomData;
2use std::mem::MaybeUninit;
3
4use crate::Observe;
5use crate::builtin::{DebugHandler, GeneralHandler, GeneralObserver, ReplaceHandler};
6use crate::helper::{AsDeref, AsDerefMut, Unsigned, Zero};
7use crate::observe::RefObserve;
8
9/// A general observer that uses snapshot comparison to detect actual value changes.
10///
11/// [`SnapshotObserver`] creates a clone of the initial value and compares it with the
12/// final value using [`PartialEq`]. This provides accurate change detection by comparing
13/// actual values rather than tracking access patterns.
14///
15/// ## Requirements
16///
17/// The observed type must implement:
18/// - [`Clone`] - for creating the snapshot
19/// - [`PartialEq`] - for comparing values
20///
21/// ## Derive Usage
22///
23/// Can be used via the `#[morphix(snapshot)]` attribute in derive macros:
24///
25/// ```
26/// # use morphix::Observe;
27/// # use serde::Serialize;
28/// # #[derive(Serialize, Observe)]
29/// # struct Uuid;
30/// # #[derive(Serialize, Observe)]
31/// # struct BitFlags;
32/// #[derive(Serialize, Observe)]
33/// struct MyStruct {
34///     #[morphix(snapshot)]
35///     id: Uuid,           // Cheap to clone and compare
36///     #[morphix(snapshot)]
37///     flags: BitFlags,    // Small Copy type
38/// }
39/// ```
40///
41/// ## When to Use
42///
43/// [`SnapshotObserver`] is ideal when:
44/// 1. The type implements [`Clone`] and [`PartialEq`] with low cost
45/// 2. Values may be modified and then restored to original (so that
46///    [`ShallowObserver`](super::ShallowObserver) would yield false positives)
47///
48/// ## Built-in Usage
49///
50/// All primitive types ([`i32`], [`f64`], [`bool`], etc.) use [`SnapshotObserver`] as their default
51/// implementation since they're cheap to clone and compare.
52pub type SnapshotObserver<'ob, S, D = Zero> = GeneralObserver<'ob, SnapshotHandler<<S as AsDeref<D>>::Target>, S, D>;
53
54/// A trait for creating and comparing snapshots of observable values.
55///
56/// [`Snapshot`] is used by [`SnapshotObserver`](crate::builtin::SnapshotObserver) to detect changes
57/// by comparing values before and after observation. It is similar to [`Clone`] + [`PartialEq`],
58/// but emphasizes serialization consistency rather than semantic equality.
59///
60/// ## Deep Copy Semantics
61///
62/// For most simple types, [`Snapshot`](Snapshot::Snapshot) is the type itself (i.e., `type Snapshot
63/// = Self`). However, for pointer types like [`Rc<T>`](std::rc::Rc), [`&T`](reference), and
64/// [`&mut T`](reference), the associated [`Snapshot`](Snapshot::Snapshot) type is `T::Snapshot`
65/// rather than `Self`. This means [`Snapshot`] performs a "deep copy" through indirections,
66/// capturing the underlying value rather than the pointer itself.
67pub trait Snapshot {
68    /// The snapshot type used for comparison.
69    ///
70    /// For value types, this is typically `Self`. For pointer and reference types, this is the
71    /// snapshot type of the pointed-to value.
72    type Snapshot;
73
74    /// Creates a snapshot of the current value.
75    ///
76    /// For pointer types, this performs a deep copy of the underlying value.
77    fn to_snapshot(&self) -> Self::Snapshot;
78
79    /// Compares the current value against a previously captured snapshot.
80    ///
81    /// Returns `true` if the current value would serialize to the same output as the snapshot,
82    /// `false` otherwise.
83    fn eq_snapshot(&self, snapshot: &Self::Snapshot) -> bool;
84}
85
86pub struct SnapshotHandler<T: Snapshot + ?Sized> {
87    snapshot: MaybeUninit<T::Snapshot>,
88    phantom: PhantomData<T>,
89}
90
91impl<T: Snapshot + ?Sized> GeneralHandler for SnapshotHandler<T> {
92    type Target = T;
93    type Spec = SnapshotSpec;
94
95    #[inline]
96    fn uninit() -> Self {
97        Self {
98            snapshot: MaybeUninit::uninit(),
99            phantom: PhantomData,
100        }
101    }
102
103    #[inline]
104    fn observe(value: &T) -> Self {
105        Self {
106            snapshot: MaybeUninit::new(value.to_snapshot()),
107            phantom: PhantomData,
108        }
109    }
110
111    #[inline]
112    fn deref_mut(&mut self) {}
113}
114
115impl<T: Snapshot + ?Sized> ReplaceHandler for SnapshotHandler<T> {
116    #[inline]
117    fn flush_replace(&mut self, value: &T) -> bool {
118        // SAFETY: `ReplaceHandler::flush_replace` is only called in `Observer::flush_unchecked`, where the
119        // observer is assumed to contain a valid pointer
120        !value.eq_snapshot(unsafe { self.snapshot.assume_init_ref() })
121    }
122}
123
124impl<T: Snapshot + ?Sized> DebugHandler for SnapshotHandler<T> {
125    const NAME: &'static str = "SnapshotObserver";
126}
127
128/// Snapshot-based observation specification.
129///
130/// [`SnapshotSpec`] marks a type as supporting efficient snapshot comparison (requires [`Clone`] +
131/// [`PartialEq`]). When used as the [`Spec`](crate::Observe::Spec) for a type `T`, it affects
132/// certain wrapper type observations, such as [`Option<T>`].
133pub struct SnapshotSpec;
134
135macro_rules! impl_snapshot_observe {
136    ($($(#[$($meta:tt)*])* $ty:ty),* $(,)?) => {
137        $(
138            $(#[$($meta)*])*
139            impl Snapshot for $ty {
140                type Snapshot = Self;
141                #[inline]
142                fn to_snapshot(&self) -> Self {
143                    *self
144                }
145                #[inline]
146                fn eq_snapshot(&self, snapshot: &Self) -> bool {
147                    self == snapshot
148                }
149            }
150
151            $(#[$($meta)*])*
152            impl Observe for $ty {
153                type Observer<'ob, S, D>
154                    = SnapshotObserver<'ob, S, D>
155                where
156                    Self: 'ob,
157                    D: Unsigned,
158                    S: AsDerefMut<D, Target = Self> + ?Sized + 'ob;
159
160                type Spec = SnapshotSpec;
161            }
162
163            $(#[$($meta)*])*
164            impl RefObserve for $ty {
165                type Observer<'ob, S, D>
166                    = SnapshotObserver<'ob, S, D>
167                where
168                    Self: 'ob,
169                    D: Unsigned,
170                    S: AsDeref<D, Target = Self> + ?Sized + 'ob;
171
172                type Spec = SnapshotSpec;
173            }
174        )*
175    };
176}
177
178impl_snapshot_observe! {
179    (), usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, f32, f64, bool, char,
180    core::net::IpAddr, core::net::Ipv4Addr, core::net::Ipv6Addr,
181    core::net::SocketAddr, core::net::SocketAddrV4, core::net::SocketAddrV6,
182    core::time::Duration, std::time::SystemTime,
183    #[cfg(feature = "chrono")] chrono::Days,
184    #[cfg(feature = "chrono")] chrono::FixedOffset,
185    #[cfg(feature = "chrono")] chrono::Month,
186    #[cfg(feature = "chrono")] chrono::Months,
187    #[cfg(feature = "chrono")] chrono::IsoWeek,
188    #[cfg(feature = "chrono")] chrono::NaiveDate,
189    #[cfg(feature = "chrono")] chrono::NaiveDateTime,
190    #[cfg(feature = "chrono")] chrono::NaiveTime,
191    #[cfg(feature = "chrono")] chrono::NaiveWeek,
192    #[cfg(feature = "chrono")] chrono::TimeDelta,
193    #[cfg(feature = "chrono")] chrono::Utc,
194    #[cfg(feature = "chrono")] chrono::Weekday,
195    #[cfg(feature = "chrono")] chrono::WeekdaySet,
196    #[cfg(feature = "uuid")] uuid::Uuid,
197    #[cfg(feature = "uuid")] uuid::NonNilUuid,
198}
199
200macro_rules! generic_impl_snapshot_observe {
201    ($($(#[$($meta:tt)*])* impl $([$($gen:tt)*])? _ for $ty:ty);* $(;)?) => {
202        $(
203            $(#[$($meta)*])*
204            impl <$($($gen)*)?> Snapshot for $ty {
205                type Snapshot = Self;
206                #[inline]
207                fn to_snapshot(&self) -> Self {
208                    self.clone()
209                }
210                #[inline]
211                fn eq_snapshot(&self, snapshot: &Self) -> bool {
212                    self == snapshot
213                }
214            }
215
216            $(#[$($meta)*])*
217            impl <$($($gen)*)?> Observe for $ty {
218                type Observer<'ob, S, D>
219                    = SnapshotObserver<'ob, S, D>
220                where
221                    Self: 'ob,
222                    D: Unsigned,
223                    S: AsDerefMut<D, Target = Self> + ?Sized + 'ob;
224
225                type Spec = SnapshotSpec;
226            }
227
228            $(#[$($meta)*])*
229            impl <$($($gen)*)?> RefObserve for $ty {
230                type Observer<'ob, S, D>
231                    = SnapshotObserver<'ob, S, D>
232                where
233                    Self: 'ob,
234                    D: Unsigned,
235                    S: AsDeref<D, Target = Self> + ?Sized + 'ob;
236
237                type Spec = SnapshotSpec;
238            }
239        )*
240    };
241}
242
243generic_impl_snapshot_observe! {
244    impl [T] _ for std::marker::PhantomData<T>;
245
246    #[cfg(feature = "chrono")]
247    impl [Tz: chrono::TimeZone] _ for chrono::DateTime<Tz>;
248}