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}