Skip to main content

meta_language/
snapshots.rs

1use std::collections::BTreeSet;
2use std::sync::Arc;
3
4use crate::access::ReadOnlyNetwork;
5use crate::link_network::{LinkId, LinkNetwork};
6
7/// Changed, added, and removed link identifiers between two network versions.
8#[derive(Clone, Debug, Default, PartialEq, Eq)]
9pub struct StructuralDiff {
10    changed: BTreeSet<LinkId>,
11    added: BTreeSet<LinkId>,
12    removed: BTreeSet<LinkId>,
13}
14
15impl StructuralDiff {
16    /// Creates a structural diff from its changed, added, and removed sets.
17    #[must_use]
18    pub const fn new(
19        changed: BTreeSet<LinkId>,
20        added: BTreeSet<LinkId>,
21        removed: BTreeSet<LinkId>,
22    ) -> Self {
23        Self {
24            changed,
25            added,
26            removed,
27        }
28    }
29
30    /// Link ids present in both versions whose references or metadata changed.
31    #[must_use]
32    pub const fn changed(&self) -> &BTreeSet<LinkId> {
33        &self.changed
34    }
35
36    /// Link ids present only in the newer version.
37    #[must_use]
38    pub const fn added(&self) -> &BTreeSet<LinkId> {
39        &self.added
40    }
41
42    /// Link ids present only in the older version.
43    #[must_use]
44    pub const fn removed(&self) -> &BTreeSet<LinkId> {
45        &self.removed
46    }
47
48    /// Whether the two versions contain the same structural links.
49    #[must_use]
50    pub fn is_empty(&self) -> bool {
51        self.changed.is_empty() && self.added.is_empty() && self.removed.is_empty()
52    }
53}
54
55/// Immutable versioned view of a links network.
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub struct NetworkSnapshot {
58    version: u64,
59    parent_version: Option<u64>,
60    provenance: String,
61    network: Arc<LinkNetwork>,
62}
63
64impl NetworkSnapshot {
65    /// Creates an immutable snapshot from a network value.
66    #[must_use]
67    pub fn new(version: u64, network: LinkNetwork, provenance: impl Into<String>) -> Self {
68        Self {
69            version,
70            parent_version: None,
71            provenance: provenance.into(),
72            network: Arc::new(network),
73        }
74    }
75
76    /// Snapshot version.
77    #[must_use]
78    pub const fn version(&self) -> u64 {
79        self.version
80    }
81
82    /// Parent version when this snapshot was committed from a mutable snapshot.
83    #[must_use]
84    pub const fn parent_version(&self) -> Option<u64> {
85        self.parent_version
86    }
87
88    /// Human-readable change provenance for this snapshot.
89    #[must_use]
90    pub fn provenance(&self) -> &str {
91        &self.provenance
92    }
93
94    /// Immutable network data held by this snapshot.
95    #[must_use]
96    pub fn network(&self) -> &LinkNetwork {
97        self.network.as_ref()
98    }
99
100    /// Number of immutable snapshot handles sharing the same network value.
101    #[must_use]
102    pub fn shared_snapshot_count(&self) -> usize {
103        Arc::strong_count(&self.network)
104    }
105
106    /// Builds an immutable snapshot from a frozen read-only view.
107    ///
108    /// The read-only view's `Arc<LinkNetwork>` is reused directly, so freezing
109    /// and snapshot versioning share one network allocation.
110    #[must_use]
111    pub fn from_read_only(
112        version: u64,
113        view: &ReadOnlyNetwork,
114        provenance: impl Into<String>,
115    ) -> Self {
116        Self {
117            version,
118            parent_version: None,
119            provenance: provenance.into(),
120            network: view.shared().clone(),
121        }
122    }
123
124    /// Returns a read-only view sharing this snapshot's network allocation.
125    #[must_use]
126    pub fn as_read_only(&self) -> ReadOnlyNetwork {
127        ReadOnlyNetwork::from_shared(self.network.clone())
128    }
129
130    /// Computes changed, added, and removed link ids against another snapshot.
131    #[must_use]
132    pub fn structural_diff(&self, other: &Self) -> StructuralDiff {
133        self.network().structural_diff(other.network())
134    }
135
136    /// Creates an editable snapshot fork from this immutable snapshot.
137    #[must_use]
138    pub fn to_mutable(&self, provenance: impl Into<String>) -> MutableNetworkSnapshot {
139        MutableNetworkSnapshot {
140            base_version: self.version,
141            network: self.network().clone(),
142            provenance: provenance.into(),
143        }
144    }
145
146    fn committed(
147        version: u64,
148        parent_version: u64,
149        network: LinkNetwork,
150        provenance: String,
151    ) -> Self {
152        Self {
153            version,
154            parent_version: Some(parent_version),
155            provenance,
156            network: Arc::new(network),
157        }
158    }
159}
160
161/// Editable fork of an immutable network snapshot.
162#[derive(Clone, Debug, PartialEq, Eq)]
163pub struct MutableNetworkSnapshot {
164    base_version: u64,
165    network: LinkNetwork,
166    provenance: String,
167}
168
169impl MutableNetworkSnapshot {
170    /// Version this mutable snapshot was forked from.
171    #[must_use]
172    pub const fn base_version(&self) -> u64 {
173        self.base_version
174    }
175
176    /// Human-readable provenance that will be attached when committed.
177    #[must_use]
178    pub fn provenance(&self) -> &str {
179        &self.provenance
180    }
181
182    /// Immutable view of the editable network.
183    #[must_use]
184    pub const fn network(&self) -> &LinkNetwork {
185        &self.network
186    }
187
188    /// Mutable view of the editable network.
189    pub fn network_mut(&mut self) -> &mut LinkNetwork {
190        &mut self.network
191    }
192
193    /// Commits this mutable snapshot as the next sequential version.
194    #[must_use]
195    pub fn commit(self) -> NetworkSnapshot {
196        let next_version = self
197            .base_version
198            .checked_add(1)
199            .expect("snapshot version overflow");
200        self.commit_as(next_version)
201    }
202
203    /// Commits this mutable snapshot with an explicit forward version.
204    #[must_use]
205    pub fn commit_as(self, version: u64) -> NetworkSnapshot {
206        assert!(
207            version > self.base_version,
208            "snapshot version must move forward"
209        );
210        NetworkSnapshot::committed(version, self.base_version, self.network, self.provenance)
211    }
212}
213
214impl LinkNetwork {
215    /// Captures the current network as an immutable versioned snapshot.
216    #[must_use]
217    pub fn snapshot(&self, version: u64, provenance: impl Into<String>) -> NetworkSnapshot {
218        NetworkSnapshot::new(version, self.clone(), provenance)
219    }
220
221    /// Computes changed, added, and removed link ids against another network.
222    #[must_use]
223    pub fn structural_diff(&self, other: &Self) -> StructuralDiff {
224        let old_ids = self
225            .links()
226            .map(crate::link_network::Link::id)
227            .collect::<BTreeSet<_>>();
228        let new_ids = other
229            .links()
230            .map(crate::link_network::Link::id)
231            .collect::<BTreeSet<_>>();
232
233        let removed = old_ids.difference(&new_ids).copied().collect();
234        let added = new_ids.difference(&old_ids).copied().collect();
235        let changed = old_ids
236            .intersection(&new_ids)
237            .copied()
238            .filter(|id| self.link(*id) != other.link(*id))
239            .collect();
240
241        StructuralDiff::new(changed, added, removed)
242    }
243}