Skip to main content

fast_mesh_store/
transaction.rs

1use crate::channel::ChannelKind;
2use crate::column::{Prepared, try_reserve_exact};
3use crate::mesh::set_bit;
4use crate::patch::{ChangeSet, Patch};
5use crate::{Mesh, Result, StoreError, TriangleId, VertexId};
6
7/// An open edit on a [`Mesh`]. All writes go through it.
8///
9/// Every method validates its whole input before writing, so a returned validation
10/// error leaves the transaction unchanged and usable. If a write itself fails (out of
11/// memory), the transaction is poisoned and `commit` rolls it back.
12///
13/// Dropping a transaction without committing rolls it back.
14pub struct Transaction<'m> {
15    mesh: &'m mut Mesh,
16    allocated_vertices: Vec<VertexId>,
17    removed_vertices: Vec<VertexId>,
18    allocated_triangles: Vec<TriangleId>,
19    removed_triangles: Vec<TriangleId>,
20    /// Slots taken from the free lists, in order, so rollback can return them.
21    popped_vertices: Vec<VertexId>,
22    popped_triangles: Vec<TriangleId>,
23    live_at_start: (usize, usize),
24    /// Ids of the channels dropped in this transaction.
25    dropped_channels: Vec<u64>,
26    /// A topology write ran outside [`Transaction::keeping_bound_channels`].
27    outdates_bound_channels: bool,
28    /// Inside [`Transaction::keeping_bound_channels`].
29    keeping_bound_channels: bool,
30    poisoned: bool,
31    finished: bool,
32}
33
34impl Mesh {
35    /// Opens a transaction. The mesh cannot be used otherwise until it ends.
36    pub fn begin_edit(&mut self) -> Transaction<'_> {
37        self.positions.begin();
38        self.triangles.begin();
39        self.vertex_live.begin();
40        self.triangle_live.begin();
41        self.vertex_refs.begin();
42        for c in &mut self.channels {
43            c.data.begin();
44        }
45        Transaction {
46            live_at_start: (self.live_vertices, self.live_triangles),
47            mesh: self,
48            allocated_vertices: Vec::new(),
49            removed_vertices: Vec::new(),
50            allocated_triangles: Vec::new(),
51            removed_triangles: Vec::new(),
52            popped_vertices: Vec::new(),
53            popped_triangles: Vec::new(),
54            dropped_channels: Vec::new(),
55            outdates_bound_channels: false,
56            keeping_bound_channels: false,
57            poisoned: false,
58            finished: false,
59        }
60    }
61}
62
63fn sorted(mut ids: Vec<u32>) -> Vec<u32> {
64    ids.sort_unstable();
65    ids.dedup();
66    ids
67}
68
69fn without(ids: Vec<u32>, a: &[u32], b: &[u32]) -> Vec<u32> {
70    ids.into_iter()
71        .filter(|i| a.binary_search(i).is_err() && b.binary_search(i).is_err())
72        .collect()
73}
74
75fn check_unique(ids: &[u32], what: &'static str) -> Result<()> {
76    let mut s = ids.to_vec();
77    s.sort_unstable();
78    if s.windows(2).any(|w| w[0] == w[1]) {
79        return Err(StoreError::InvalidArgument(what));
80    }
81    Ok(())
82}
83
84/// Patch vectors of every journaled column, built before any journal is consumed.
85struct PreparedColumns {
86    positions: Prepared<[f32; 3]>,
87    triangles: Prepared<[u32; 3]>,
88    vertex_live: Prepared<u64>,
89    triangle_live: Prepared<u64>,
90    vertex_refs: Prepared<u32>,
91    /// Per channel, in `mesh.channels` order: `None` for derived channels.
92    channels: Vec<Option<Prepared<f32>>>,
93}
94
95impl PreparedColumns {
96    fn new(m: &Mesh) -> Result<Self> {
97        let mut channels = Vec::new();
98        try_reserve_exact(&mut channels, m.channels.len())?;
99        for c in &m.channels {
100            channels.push(match c.desc.kind {
101                ChannelKind::Source => Some(c.data.prepare_finish()?),
102                ChannelKind::Derived => None,
103            });
104        }
105        Ok(Self {
106            positions: m.positions.prepare_finish()?,
107            triangles: m.triangles.prepare_finish()?,
108            vertex_live: m.vertex_live.prepare_finish()?,
109            triangle_live: m.triangle_live.prepare_finish()?,
110            vertex_refs: m.vertex_refs.prepare_finish()?,
111            channels,
112        })
113    }
114}
115
116impl Transaction<'_> {
117    /// Read access to the mesh as edited so far.
118    pub fn mesh(&self) -> &Mesh {
119        self.mesh
120    }
121
122    fn check_vertex(&self, v: VertexId) -> Result<()> {
123        if self.mesh.is_vertex_alive(v) {
124            Ok(())
125        } else {
126            Err(StoreError::InvalidVertex(v))
127        }
128    }
129
130    fn check_triangle(&self, t: TriangleId) -> Result<()> {
131        if self.mesh.is_triangle_alive(t) {
132            Ok(())
133        } else {
134            Err(StoreError::InvalidTriangle(t))
135        }
136    }
137
138    /// Runs the write phase of an operation; any failure poisons the transaction.
139    fn write<R>(&mut self, f: impl FnOnce(&mut Self) -> Result<R>) -> Result<R> {
140        if self.poisoned {
141            return Err(StoreError::TransactionPoisoned);
142        }
143        let r = f(self);
144        if r.is_err() {
145            self.poisoned = true;
146        }
147        r
148    }
149
150    fn alloc_vertex(
151        &mut self,
152        position: [f32; 3],
153        parents: &[(VertexId, f32)],
154    ) -> Result<VertexId> {
155        let m = &mut *self.mesh;
156        let id = match m.vertex_free.pop() {
157            Some(id) => {
158                self.popped_vertices.push(id);
159                debug_assert!(!m.is_vertex_alive(id), "free list held alive vertex {id}");
160                m.positions.set(id as usize, position);
161                id
162            }
163            None => {
164                let id = m.positions.push(position)?;
165                m.vertex_refs.push(0)?;
166                for c in &mut m.channels {
167                    for _ in 0..c.desc.width {
168                        c.data.push(c.desc.default)?;
169                    }
170                }
171                u32::try_from(id).map_err(|_| StoreError::OutOfMemory)?
172            }
173        };
174        for c in &mut m.channels {
175            let w = c.desc.width;
176            for k in 0..w {
177                let value = c.initial_value(parents, k);
178                c.data.set(id as usize * w + k, value);
179            }
180        }
181        set_bit(&mut m.vertex_live, id as usize, true)?;
182        m.live_vertices += 1;
183        self.allocated_vertices.push(id);
184        Ok(id)
185    }
186
187    /// Runs `f`, whose topology writes do not outdate
188    /// [`topology_bound`](crate::ChannelDesc::topology_bound) channels: for an
189    /// operation that keeps those channels true itself. Writes outside it, in
190    /// the same transaction, still do.
191    pub fn keeping_bound_channels<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
192        let outer = std::mem::replace(&mut self.keeping_bound_channels, true);
193        let r = f(self);
194        self.keeping_bound_channels = outer;
195        r
196    }
197
198    fn alloc_triangle(&mut self, tri: [VertexId; 3]) -> Result<TriangleId> {
199        self.outdates_bound_channels |= !self.keeping_bound_channels;
200        let m = &mut *self.mesh;
201        m.on_topology_write();
202        let id = match m.triangle_free.pop() {
203            Some(id) => {
204                self.popped_triangles.push(id);
205                debug_assert!(
206                    !m.is_triangle_alive(id),
207                    "free list held alive triangle {id}"
208                );
209                m.triangles.set(id as usize, tri);
210                id
211            }
212            None => {
213                let id = m.triangles.push(tri)?;
214                u32::try_from(id).map_err(|_| StoreError::OutOfMemory)?
215            }
216        };
217        for v in tri {
218            let r = m.vertex_refs.get(v as usize);
219            m.vertex_refs.set(v as usize, r + 1);
220        }
221        set_bit(&mut m.triangle_live, id as usize, true)?;
222        m.live_triangles += 1;
223        self.allocated_triangles.push(id);
224        Ok(id)
225    }
226
227    fn release_corners(&mut self, tri: [VertexId; 3]) {
228        let m = &mut *self.mesh;
229        for v in tri {
230            let r = m.vertex_refs.get(v as usize);
231            m.vertex_refs.set(v as usize, r - 1);
232        }
233    }
234
235    /// Adds vertices without creation parents; source channels get their default value.
236    pub fn add_vertices(&mut self, positions: &[[f32; 3]]) -> Result<Vec<VertexId>> {
237        self.write(|tx| positions.iter().map(|&p| tx.alloc_vertex(p, &[])).collect())
238    }
239
240    /// Adds a vertex created from existing vertices, e.g. an edge split creates a vertex
241    /// with parents `[(a, 0.5), (b, 0.5)]`. Source channels derive its values from the
242    /// parents according to their [`crate::TopologyPolicy`].
243    pub fn add_vertex_with_parents(
244        &mut self,
245        position: [f32; 3],
246        parents: &[(VertexId, f32)],
247    ) -> Result<VertexId> {
248        for &(v, w) in parents {
249            self.check_vertex(v)?;
250            if !w.is_finite() || w < 0.0 {
251                return Err(StoreError::InvalidArgument(
252                    "parent weights must be finite and non-negative",
253                ));
254            }
255        }
256        self.write(|tx| tx.alloc_vertex(position, parents))
257    }
258
259    /// Sets positions of existing vertices.
260    pub fn set_positions(&mut self, ids: &[VertexId], positions: &[[f32; 3]]) -> Result<()> {
261        if ids.len() != positions.len() {
262            return Err(StoreError::LengthMismatch {
263                expected: ids.len(),
264                got: positions.len(),
265            });
266        }
267        for &v in ids {
268            self.check_vertex(v)?;
269        }
270        self.write(|tx| {
271            for (&v, &p) in ids.iter().zip(positions) {
272                tx.mesh.positions.set(v as usize, p);
273            }
274            Ok(())
275        })
276    }
277
278    /// Removes vertices that no triangle uses.
279    pub fn remove_vertices(&mut self, ids: &[VertexId]) -> Result<()> {
280        check_unique(ids, "duplicate vertex id")?;
281        for &v in ids {
282            self.check_vertex(v)?;
283            let corners = self.mesh.corner_count(v);
284            if corners > 0 {
285                return Err(StoreError::VertexInUse { vertex: v, corners });
286            }
287        }
288        self.write(|tx| {
289            for &v in ids {
290                set_bit(&mut tx.mesh.vertex_live, v as usize, false)?;
291                tx.mesh.live_vertices -= 1;
292            }
293            tx.removed_vertices.extend_from_slice(ids);
294            Ok(())
295        })
296    }
297
298    /// Adds triangles between existing vertices.
299    pub fn add_triangles(&mut self, triangles: &[[VertexId; 3]]) -> Result<Vec<TriangleId>> {
300        for t in triangles {
301            for &v in t {
302                self.check_vertex(v)?;
303            }
304        }
305        self.write(|tx| triangles.iter().map(|&t| tx.alloc_triangle(t)).collect())
306    }
307
308    /// Removes triangles. Their vertices stay; remove them separately if unused.
309    pub fn remove_triangles(&mut self, ids: &[TriangleId]) -> Result<()> {
310        check_unique(ids, "duplicate triangle id")?;
311        for &t in ids {
312            self.check_triangle(t)?;
313        }
314        self.write(|tx| {
315            if !ids.is_empty() {
316                tx.outdates_bound_channels |= !tx.keeping_bound_channels;
317                tx.mesh.on_topology_write();
318            }
319            for &t in ids {
320                let tri = tx.mesh.triangles.get(t as usize);
321                tx.release_corners(tri);
322                tx.mesh.triangles.set(t as usize, [0; 3]);
323                set_bit(&mut tx.mesh.triangle_live, t as usize, false)?;
324                tx.mesh.live_triangles -= 1;
325            }
326            tx.removed_triangles.extend_from_slice(ids);
327            Ok(())
328        })
329    }
330
331    /// Replaces the vertex ids of existing triangles (e.g. an edge flip).
332    pub fn set_triangles(&mut self, ids: &[TriangleId], triangles: &[[VertexId; 3]]) -> Result<()> {
333        if ids.len() != triangles.len() {
334            return Err(StoreError::LengthMismatch {
335                expected: ids.len(),
336                got: triangles.len(),
337            });
338        }
339        check_unique(ids, "duplicate triangle id")?;
340        for (&t, tri) in ids.iter().zip(triangles) {
341            self.check_triangle(t)?;
342            for &v in tri {
343                self.check_vertex(v)?;
344            }
345        }
346        self.write(|tx| {
347            if !ids.is_empty() {
348                tx.outdates_bound_channels |= !tx.keeping_bound_channels;
349                tx.mesh.on_topology_write();
350            }
351            for (&t, &tri) in ids.iter().zip(triangles) {
352                let old = tx.mesh.triangles.get(t as usize);
353                tx.release_corners(old);
354                for v in tri {
355                    let r = tx.mesh.vertex_refs.get(v as usize);
356                    tx.mesh.vertex_refs.set(v as usize, r + 1);
357                }
358                tx.mesh.triangles.set(t as usize, tri);
359            }
360            Ok(())
361        })
362    }
363
364    /// Writes source channel values: `values` holds `width` values per vertex in `ids`.
365    pub fn write_channel(&mut self, name: &str, ids: &[VertexId], values: &[f32]) -> Result<()> {
366        let ci = self
367            .mesh
368            .channel_index(name)
369            .ok_or_else(|| StoreError::UnknownChannel(name.into()))?;
370        let desc = &self.mesh.channels[ci].desc;
371        if desc.kind != ChannelKind::Source {
372            return Err(StoreError::NotSourceChannel(name.into()));
373        }
374        let w = desc.width;
375        if values.len() != ids.len() * w {
376            return Err(StoreError::LengthMismatch {
377                expected: ids.len() * w,
378                got: values.len(),
379            });
380        }
381        for &v in ids {
382            self.check_vertex(v)?;
383        }
384        self.write(|tx| {
385            let data = &mut tx.mesh.channels[ci].data;
386            for (n, &v) in ids.iter().enumerate() {
387                for k in 0..w {
388                    data.set(v as usize * w + k, values[n * w + k]);
389                }
390            }
391            Ok(())
392        })
393    }
394
395    /// Drops a source channel as part of this edit: it reads as absent from
396    /// now on, undoing the edit restores it with its values, and redoing drops
397    /// it again. For an operation whose new values for the channel are not
398    /// well defined, where leaving it would mean stale values. The values stay
399    /// in memory, counted in [`Patch::byte_size`], until the patch is released
400    /// from its history or the mesh is compacted. A channel added later under
401    /// the same name is replaced when the edit is undone.
402    ///
403    /// # Errors
404    /// [`StoreError::UnknownChannel`], or [`StoreError::NotSourceChannel`] for
405    /// a derived channel; the transaction stays usable.
406    pub fn drop_channel(&mut self, name: &str) -> Result<()> {
407        let ci = self
408            .mesh
409            .channel_index(name)
410            .ok_or_else(|| StoreError::UnknownChannel(name.into()))?;
411        if self.mesh.channels[ci].desc.kind != ChannelKind::Source {
412            return Err(StoreError::NotSourceChannel(name.into()));
413        }
414        self.write(|tx| {
415            try_reserve_exact(&mut tx.dropped_channels, 1)?;
416            let channel = &mut tx.mesh.channels[ci];
417            channel.hidden = true;
418            tx.dropped_channels.push(channel.id);
419            Ok(())
420        })
421    }
422
423    /// Ends the transaction and returns its patch. An empty transaction returns an
424    /// empty patch and leaves the revision unchanged. If memory for the patch runs
425    /// out, the transaction is rolled back and `OutOfMemory` is returned.
426    pub fn commit(mut self) -> Result<Patch> {
427        self.finished = true;
428        if self.poisoned {
429            self.rollback_inner();
430            return Err(StoreError::TransactionPoisoned);
431        }
432        // Phase 1 (fallible, writes nothing): build every patch vector while all
433        // journals are intact. If memory runs out, roll back as if never committed.
434        if self.outdates_bound_channels
435            && try_reserve_exact(&mut self.dropped_channels, self.mesh.channels.len()).is_err()
436        {
437            self.rollback_inner();
438            return Err(StoreError::OutOfMemory);
439        }
440        let prepared = match PreparedColumns::new(self.mesh) {
441            Ok(p) => p,
442            Err(e) => {
443                self.rollback_inner();
444                return Err(e);
445            }
446        };
447        // Phase 2 (infallible): consume the journals.
448        let m = &mut *self.mesh;
449        if self.outdates_bound_channels {
450            for c in &mut m.channels {
451                if c.desc.topology_bound && !c.hidden {
452                    c.hidden = true;
453                    self.dropped_channels.push(c.id);
454                }
455            }
456        }
457
458        // Slots allocated and removed within this transaction were never visible:
459        // they go straight back to the free lists.
460        let allocated_v = sorted(std::mem::take(&mut self.allocated_vertices));
461        let (created_v, transient_v): (Vec<_>, Vec<_>) = allocated_v
462            .iter()
463            .copied()
464            .partition(|&v| m.is_vertex_alive(v));
465        let allocated_t = sorted(std::mem::take(&mut self.allocated_triangles));
466        let (created_t, transient_t): (Vec<_>, Vec<_>) = allocated_t
467            .iter()
468            .copied()
469            .partition(|&t| m.is_triangle_alive(t));
470        m.vertex_free.extend_from_slice(&transient_v);
471        m.triangle_free.extend_from_slice(&transient_t);
472
473        let freed_v = without(
474            sorted(std::mem::take(&mut self.removed_vertices)),
475            &allocated_v,
476            &[],
477        );
478        let freed_t = without(
479            sorted(std::mem::take(&mut self.removed_triangles)),
480            &allocated_t,
481            &[],
482        );
483        // Moved vertices, rewritten triangles and channel writes come from the column
484        // journals, which hold each pre-existing index once however often it was
485        // written. Slot reuse by `alloc_*` and the `[0, 0, 0]` write of a removal also
486        // journal their slot; filtering out created and freed ids removes those.
487        let moved = without(
488            sorted(m.positions.journal_ids().to_vec()),
489            &allocated_v,
490            &freed_v,
491        );
492        let changed_t = without(
493            sorted(m.triangles.journal_ids().to_vec()),
494            &allocated_t,
495            &freed_t,
496        );
497
498        let mut channels = Vec::new();
499        let mut channel_data = Vec::new();
500        for (c, prepared) in m.channels.iter_mut().zip(prepared.channels) {
501            let Some(prepared) = prepared else {
502                c.data.end_journal(); // derived: not undoable
503                continue;
504            };
505            let w = c.desc.width as u32;
506            let written = c.data.journal_ids().iter().map(|&i| i / w).collect();
507            let written = without(sorted(written), &allocated_v, &freed_v);
508            if !written.is_empty() && !c.hidden {
509                channels.push((c.desc.name.clone(), written));
510            }
511            let patch = c.data.seal(prepared);
512            if !patch.is_empty() {
513                channel_data.push((c.id, patch));
514            }
515        }
516
517        let dropped_channels = std::mem::take(&mut self.dropped_channels);
518        let dropped = |c: &&crate::channel::Channel| dropped_channels.contains(&c.id);
519        let mut channels_toggled: Vec<String> = m
520            .channels
521            .iter()
522            .filter(dropped)
523            .map(|c| c.desc.name.clone())
524            .collect();
525        channels_toggled.sort_unstable();
526        channels_toggled.dedup();
527        let dropped_bytes = m
528            .channels
529            .iter()
530            .filter(dropped)
531            .map(|c| c.data.len() * size_of::<f32>())
532            .sum();
533        let changes = ChangeSet {
534            vertices_added: created_v,
535            vertices_removed: freed_v,
536            vertices_moved: moved,
537            triangles_added: created_t,
538            triangles_removed: freed_t,
539            triangles_changed: changed_t,
540            channels,
541            channels_toggled,
542        };
543        if m.incidence_pending {
544            // The journal still holds every rewritten triangle's old vertices.
545            m.incidence_pending = false;
546            let c = &changes;
547            let ids = [&c.triangles_added[..], &c.triangles_changed[..]];
548            m.with_incidence(|inc, m| {
549                for (t, tri) in m.triangles.journal_entries() {
550                    inc.remove(t, tri)?;
551                }
552                inc.insert_alive(m, &ids)
553            });
554        }
555        let base_revision = m.revision;
556        if !changes.is_empty() {
557            m.next_revision += 1;
558            m.revision = m.next_revision;
559        }
560        let patch = Patch {
561            mesh_uid: m.uid,
562            epoch: m.epoch,
563            base_revision,
564            revision: m.revision,
565            positions: m.positions.seal(prepared.positions),
566            triangles: m.triangles.seal(prepared.triangles),
567            vertex_live: m.vertex_live.seal(prepared.vertex_live),
568            triangle_live: m.triangle_live.seal(prepared.triangle_live),
569            vertex_refs: m.vertex_refs.seal(prepared.vertex_refs),
570            channel_data,
571            dropped_channels,
572            dropped_bytes,
573            channels_before: m.next_channel_id,
574            changes,
575        };
576        Ok(patch)
577    }
578
579    /// Discards every change made in this transaction.
580    pub fn rollback(mut self) {
581        self.finished = true;
582        self.rollback_inner();
583    }
584
585    fn rollback_inner(&mut self) {
586        let m = &mut *self.mesh;
587        m.positions.rollback();
588        m.triangles.rollback();
589        m.vertex_live.rollback();
590        m.triangle_live.rollback();
591        m.vertex_refs.rollback();
592        for c in &mut m.channels {
593            c.data.rollback();
594            if self.dropped_channels.contains(&c.id) {
595                c.hidden = false;
596            }
597        }
598        while let Some(v) = self.popped_vertices.pop() {
599            m.vertex_free.push(v);
600        }
601        while let Some(t) = self.popped_triangles.pop() {
602            m.triangle_free.push(t);
603        }
604        (m.live_vertices, m.live_triangles) = self.live_at_start;
605        // The columns are as they were, so the index is current again.
606        m.incidence_pending = false;
607    }
608}
609
610impl Drop for Transaction<'_> {
611    fn drop(&mut self) {
612        if !self.finished {
613            self.rollback_inner();
614        }
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use crate::column::fail_allocations_after;
621    use crate::{ChannelDesc, Mesh, StoreError, TopologyPolicy};
622
623    fn quad() -> Mesh {
624        Mesh::from_buffers(
625            &[0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 0.],
626            &[0, 1, 2, 0, 2, 3],
627        )
628        .unwrap()
629    }
630
631    /// A quad with a UV per vertex: `u = 10 * v`, `v = v`.
632    fn quad_uv() -> Mesh {
633        let mut m = quad();
634        m.add_uvs(&[0., 0., 10., 1., 20., 2., 30., 3.]).unwrap();
635        m
636    }
637
638    #[test]
639    fn add_uvs_checks_its_input() {
640        let mut m = quad();
641        assert_eq!(
642            m.add_uvs(&[0.; 7]),
643            Err(StoreError::LengthMismatch {
644                expected: 8,
645                got: 7
646            })
647        );
648        let mut bad = [0.; 8];
649        bad[3] = f32::NAN;
650        assert!(matches!(
651            m.add_uvs(&bad),
652            Err(StoreError::InvalidArgument(_))
653        ));
654        assert!(m.uvs().is_none(), "a failed add leaves no channel");
655        m.add_uvs(&[0.; 8]).unwrap();
656        assert_eq!(
657            m.add_uvs(&[0.; 8]),
658            Err(StoreError::ChannelExists("uv".into()))
659        );
660        assert!(matches!(
661            quad().add_channel(ChannelDesc::source("uv", 3)),
662            Err(StoreError::InvalidArgument(_))
663        ));
664    }
665
666    #[test]
667    fn dropping_a_channel_undoes_and_redoes_with_its_values() {
668        let mut m = quad_uv();
669        let before = m.uvs().unwrap().to_vec();
670        let mut tx = m.begin_edit();
671        tx.drop_channel("uv").unwrap();
672        // New slots keep the hidden column aligned.
673        let v = tx.add_vertices(&[[5.; 3]]).unwrap()[0];
674        tx.add_triangles(&[[0, 1, v]]).unwrap();
675        assert!(tx.mesh().uvs().is_none());
676        let patch = tx.commit().unwrap();
677        assert_eq!(patch.changes().channels_toggled, vec!["uv".to_string()]);
678        assert!(m.uvs().is_none());
679        assert!(patch.byte_size() >= before.len() * 4);
680
681        let undo = patch.revert(&mut m).unwrap();
682        assert_eq!(undo.channels_toggled, vec!["uv".to_string()]);
683        let back = m.uvs().unwrap();
684        assert_eq!(&back[..8], &before[..]);
685        assert_eq!(back.len(), 2 * m.vertex_slots());
686
687        patch.apply(&mut m).unwrap();
688        assert!(m.uvs().is_none());
689    }
690
691    #[test]
692    fn a_drop_alone_is_a_change_and_rolls_back() {
693        let mut m = quad_uv();
694        let revision = m.revision();
695        let mut tx = m.begin_edit();
696        tx.drop_channel("uv").unwrap();
697        tx.rollback();
698        assert!(m.uvs().is_some());
699        assert_eq!(m.revision(), revision);
700        {
701            let mut tx = m.begin_edit();
702            tx.drop_channel("uv").unwrap();
703        }
704        assert!(m.uvs().is_some(), "dropping the transaction rolls back");
705        let mut tx = m.begin_edit();
706        tx.drop_channel("uv").unwrap();
707        assert!(tx.drop_channel("uv").is_err(), "already gone");
708        let patch = tx.commit().unwrap();
709        assert!(!patch.is_empty());
710        assert_ne!(m.revision(), revision);
711    }
712
713    #[test]
714    fn a_derived_channel_cannot_be_dropped() {
715        let mut m = quad();
716        m.add_channel(ChannelDesc::derived("d", 1)).unwrap();
717        let mut tx = m.begin_edit();
718        assert_eq!(
719            tx.drop_channel("d"),
720            Err(StoreError::NotSourceChannel("d".into()))
721        );
722        assert_eq!(
723            tx.drop_channel("nope"),
724            Err(StoreError::UnknownChannel("nope".into()))
725        );
726    }
727
728    #[test]
729    fn undoing_a_drop_replaces_a_newer_channel_of_that_name() {
730        let mut m = quad_uv();
731        let before = m.uvs().unwrap().to_vec();
732        let mut tx = m.begin_edit();
733        tx.drop_channel("uv").unwrap();
734        let patch = tx.commit().unwrap();
735        m.add_uvs(&[7.; 8]).unwrap();
736        patch.revert(&mut m).unwrap();
737        assert_eq!(m.uvs().unwrap(), &before[..]);
738    }
739
740    #[test]
741    fn releasing_a_drop_frees_the_values_and_compact_forgets_them() {
742        use crate::PatchSide;
743        let mut m = quad_uv();
744        let mut tx = m.begin_edit();
745        tx.drop_channel("uv").unwrap();
746        let patch = tx.commit().unwrap();
747        assert_eq!(m.channels.len(), 1, "hidden, kept for undo");
748        patch.release(&mut m, PatchSide::Undoable);
749        assert!(m.channels.is_empty());
750
751        let mut m = quad_uv();
752        let mut tx = m.begin_edit();
753        tx.drop_channel("uv").unwrap();
754        tx.commit().unwrap();
755        m.compact().unwrap();
756        assert!(m.channels.is_empty());
757    }
758
759    #[test]
760    fn uvs_interpolate_and_undo_like_any_source_channel() {
761        let mut m = quad_uv();
762        let mut tx = m.begin_edit();
763        let v = tx
764            .add_vertex_with_parents([0.5, 0.0, 0.0], &[(0, 0.5), (1, 0.5)])
765            .unwrap();
766        tx.write_channel("uv", &[2], &[-1., -2.]).unwrap();
767        let patch = tx.commit().unwrap();
768        assert_eq!(&m.uvs().unwrap()[2 * v as usize..][..2], &[5., 0.5]);
769        assert_eq!(&m.uvs().unwrap()[4..6], &[-1., -2.]);
770        patch.revert(&mut m).unwrap();
771        assert_eq!(&m.uvs().unwrap()[4..6], &[20., 2.]);
772    }
773
774    #[test]
775    fn moving_vertices_commits_a_reversible_patch() {
776        let mut m = quad();
777        let mut tx = m.begin_edit();
778        tx.set_positions(&[2], &[[5., 5., 5.]]).unwrap();
779        let patch = tx.commit().unwrap();
780        assert_eq!(m.position(2), Some([5., 5., 5.]));
781        assert_eq!(patch.changes().vertices_moved, vec![2]);
782        assert!(!patch.changes().topology_changed());
783
784        let undo = patch.revert(&mut m).unwrap();
785        assert_eq!(m.position(2), Some([1., 1., 0.]));
786        assert_eq!(undo.vertices_moved, vec![2]);
787        patch.apply(&mut m).unwrap();
788        assert_eq!(m.position(2), Some([5., 5., 5.]));
789    }
790
791    #[test]
792    fn adding_and_removing_topology_round_trips() {
793        let mut m = quad();
794        let mut tx = m.begin_edit();
795        let v = tx.add_vertices(&[[2., 0., 0.]]).unwrap()[0];
796        let t = tx.add_triangles(&[[1, v, 2]]).unwrap()[0];
797        tx.remove_triangles(&[0]).unwrap();
798        let patch = tx.commit().unwrap();
799        assert_eq!((v, t), (4, 2));
800        assert_eq!((m.vertex_count(), m.triangle_count()), (5, 2));
801        assert_eq!(
802            m.triangles()[0],
803            [0, 0, 0],
804            "removed triangles read as degenerate"
805        );
806        assert_eq!(m.corner_count(1), 1);
807
808        let undo = patch.revert(&mut m).unwrap();
809        assert_eq!(undo.triangles_added, vec![0]);
810        assert_eq!(undo.triangles_removed, vec![2]);
811        assert_eq!(undo.vertices_removed, vec![4]);
812        assert_eq!((m.vertex_count(), m.triangle_count()), (4, 2));
813        assert_eq!(m.triangle(0), Some([0, 1, 2]));
814        assert!(!m.is_vertex_alive(4));
815        assert_eq!(m.corner_count(1), 1);
816    }
817
818    #[test]
819    fn validation_errors_leave_the_transaction_usable() {
820        let mut m = quad();
821        let mut tx = m.begin_edit();
822        assert_eq!(
823            tx.set_positions(&[9], &[[0.; 3]]),
824            Err(StoreError::InvalidVertex(9))
825        );
826        assert_eq!(
827            tx.remove_vertices(&[0]),
828            Err(StoreError::VertexInUse {
829                vertex: 0,
830                corners: 2
831            })
832        );
833        assert!(matches!(
834            tx.remove_triangles(&[0, 0]),
835            Err(StoreError::InvalidArgument(_))
836        ));
837        tx.set_positions(&[0], &[[1., 2., 3.]]).unwrap();
838        tx.commit().unwrap();
839        assert_eq!(m.position(0), Some([1., 2., 3.]));
840    }
841
842    #[test]
843    fn rollback_and_drop_discard_everything() {
844        let mut m = quad();
845        let before = m.revision();
846        let mut tx = m.begin_edit();
847        tx.add_vertices(&[[9.; 3]]).unwrap();
848        tx.remove_triangles(&[1]).unwrap();
849        tx.rollback();
850        assert_eq!(
851            (m.vertex_slots(), m.vertex_count(), m.triangle_count()),
852            (4, 4, 2)
853        );
854        assert_eq!(m.triangle(1), Some([0, 2, 3]));
855        assert_eq!(m.revision(), before);
856
857        {
858            let mut tx = m.begin_edit();
859            tx.set_positions(&[0], &[[7.; 3]]).unwrap();
860        }
861        assert_eq!(m.position(0), Some([0., 0., 0.]));
862    }
863
864    #[test]
865    fn empty_transactions_keep_the_revision() {
866        let mut m = quad();
867        let patch = m.begin_edit().commit().unwrap();
868        assert!(patch.is_empty());
869        assert_eq!(m.revision(), 0);
870    }
871
872    #[test]
873    fn new_vertices_take_channel_values_from_parents() {
874        let mut m = quad();
875        m.add_channel(ChannelDesc::source("mask", 1)).unwrap();
876        m.add_channel(ChannelDesc::source("id", 1).with_policy(TopologyPolicy::Nearest))
877            .unwrap();
878        let mut tx = m.begin_edit();
879        tx.write_channel("mask", &[0, 1], &[2.0, 4.0]).unwrap();
880        tx.write_channel("id", &[0, 1], &[10.0, 20.0]).unwrap();
881        let v = tx
882            .add_vertex_with_parents([0.5, 0., 0.], &[(0, 0.75), (1, 0.25)])
883            .unwrap();
884        let patch = tx.commit().unwrap();
885        assert_eq!(m.channel("mask").unwrap()[v as usize], 2.5);
886        assert_eq!(m.channel("id").unwrap()[v as usize], 10.0);
887        assert_eq!(
888            patch.changes().channels,
889            vec![
890                ("mask".to_string(), vec![0, 1]),
891                ("id".to_string(), vec![0, 1])
892            ]
893        );
894        patch.revert(&mut m).unwrap();
895        assert_eq!(&m.channel("mask").unwrap()[..2], &[0.0, 0.0]);
896    }
897
898    #[test]
899    fn patches_skip_a_channel_that_was_removed_and_re_added_under_the_same_name() {
900        // Width change (used to panic) and equal width (used to corrupt the new channel).
901        for new_width in [1, 4] {
902            let mut m = quad();
903            m.add_channel(ChannelDesc::source("c", 4)).unwrap();
904            let mut tx = m.begin_edit();
905            tx.write_channel("c", &[0, 1], &[1.0; 8]).unwrap();
906            let patch = tx.commit().unwrap();
907            m.remove_channel("c").unwrap();
908            m.add_channel(ChannelDesc::source("c", new_width).with_default(9.0))
909                .unwrap();
910            let untouched = vec![9.0; 4 * new_width];
911            patch.revert(&mut m).unwrap();
912            assert_eq!(m.channel("c").unwrap(), &untouched[..], "width {new_width}");
913            patch.apply(&mut m).unwrap();
914            assert_eq!(m.channel("c").unwrap(), &untouched[..], "width {new_width}");
915        }
916    }
917
918    #[test]
919    fn commit_that_runs_out_of_memory_rolls_back_everything() {
920        type State = (Vec<[f32; 3]>, Vec<[u32; 3]>, Vec<f32>, usize, usize, u64);
921        fn state(m: &Mesh) -> State {
922            (
923                m.positions()[..4].to_vec(),
924                m.triangles()[..2].to_vec(),
925                m.channel("c").unwrap()[..4].to_vec(),
926                m.vertex_count(),
927                m.triangle_count(),
928                m.revision(),
929            )
930        }
931        // Sparse patches, and snapshot patches (every position rewritten).
932        for moved in [vec![1], vec![0, 1, 2, 3]] {
933            let mut m = quad();
934            m.add_channel(ChannelDesc::source("c", 1)).unwrap();
935            m.add_channel(ChannelDesc::derived("d", 1)).unwrap();
936            let before = state(&m);
937            for k in 0.. {
938                // A journal left behind by a failed commit would trip `begin()`.
939                let mut tx = m.begin_edit();
940                tx.set_positions(&moved, &vec![[7.; 3]; moved.len()])
941                    .unwrap();
942                let v = tx.add_vertices(&[[1.; 3]]).unwrap()[0];
943                tx.add_triangles(&[[0, 1, v]]).unwrap();
944                tx.remove_triangles(&[1]).unwrap();
945                tx.write_channel("c", &[0], &[3.]).unwrap();
946                fail_allocations_after(Some(k));
947                let result = tx.commit();
948                fail_allocations_after(None);
949                match result {
950                    Err(e) => {
951                        assert_eq!(e, StoreError::OutOfMemory);
952                        assert_eq!(state(&m), before, "failed after {k} allocations");
953                        assert_eq!(m.vertex_slots(), 4, "appended slots truncated");
954                        assert_eq!(m.channel("d").unwrap().len(), 4);
955                    }
956                    Ok(patch) => {
957                        assert!(k > 0, "commit needs to allocate");
958                        assert_eq!(m.position(1), Some([7.; 3]));
959                        assert_eq!(m.triangle(1), None);
960                        assert_eq!(m.channel("c").unwrap()[0], 3.);
961                        // The journals were intact: the patch restores everything.
962                        patch.revert(&mut m).unwrap();
963                        assert_eq!(state(&m), before);
964                        break;
965                    }
966                }
967            }
968        }
969    }
970
971    #[test]
972    fn repeated_writes_report_each_element_once() {
973        let mut m = quad();
974        m.add_channel(ChannelDesc::source("rgb", 3)).unwrap();
975        // Free slots 1 (triangle) and 3 (vertex), so the transaction reuses them.
976        let mut tx = m.begin_edit();
977        tx.remove_triangles(&[1]).unwrap();
978        tx.remove_vertices(&[3]).unwrap();
979        let p = tx.commit().unwrap();
980        // A zero budget evicts the patch at once, which releases its slots.
981        crate::History::new(0).push(&mut m, p).unwrap();
982        assert_eq!(m.free_vertex_slots(), &[3]);
983
984        let mut tx = m.begin_edit();
985        for round in 0..200 {
986            let x = round as f32;
987            tx.set_positions(&[2, 0, 2], &[[x; 3]; 3]).unwrap();
988            tx.write_channel("rgb", &[1, 2], &[x; 6]).unwrap();
989            tx.set_triangles(&[0], &[[0, 1, 2]]).unwrap();
990        }
991        let v = tx.add_vertices(&[[9.; 3]]).unwrap()[0];
992        let t = tx.add_triangles(&[[0, 2, v]]).unwrap()[0];
993        tx.set_positions(&[v], &[[8.; 3]]).unwrap();
994        tx.write_channel("rgb", &[v], &[1.; 3]).unwrap();
995        tx.set_triangles(&[t], &[[0, 1, v]]).unwrap();
996        let patch = tx.commit().unwrap();
997        assert_eq!((v, t), (3, 1), "free slots were reused");
998        let c = patch.changes();
999        assert_eq!(c.vertices_added, vec![3]);
1000        assert_eq!(c.vertices_moved, vec![0, 2]);
1001        assert_eq!(c.triangles_added, vec![1]);
1002        assert_eq!(c.triangles_changed, vec![0]);
1003        assert_eq!(c.channels, vec![("rgb".to_string(), vec![1, 2])]);
1004        let undo = patch.revert(&mut m).unwrap();
1005        assert_eq!(undo.vertices_moved, vec![0, 2]);
1006        assert_eq!(undo.vertices_removed, vec![3]);
1007    }
1008
1009    #[test]
1010    fn topology_writes_drop_the_adjacency_cache_inside_the_transaction() {
1011        type Edit = fn(&mut crate::Transaction<'_>);
1012        let edits: [Edit; 3] = [
1013            |tx| {
1014                tx.add_triangles(&[[1, 2, 3]]).unwrap();
1015            },
1016            |tx| tx.remove_triangles(&[1]).unwrap(),
1017            |tx| tx.set_triangles(&[1], &[[1, 2, 3]]).unwrap(),
1018        ];
1019        for edit in edits {
1020            let mut m = quad();
1021            m.ensure_adjacency().unwrap();
1022            let mut tx = m.begin_edit();
1023            tx.set_positions(&[0], &[[0., 0., 1.]]).unwrap();
1024            assert!(tx.mesh().adjacency().is_some(), "moves keep the cache");
1025            edit(&mut tx);
1026            assert!(tx.mesh().adjacency().is_none());
1027            tx.rollback();
1028            m.ensure_adjacency().unwrap();
1029            assert_eq!(
1030                m.adjacency().unwrap().edge_kind(1),
1031                crate::EdgeKind::Interior,
1032                "rebuilt from the rolled-back mesh"
1033            );
1034        }
1035    }
1036
1037    #[test]
1038    fn writing_a_derived_channel_in_a_transaction_is_rejected() {
1039        let mut m = quad();
1040        m.add_channel(ChannelDesc::derived("dist", 1)).unwrap();
1041        let mut tx = m.begin_edit();
1042        assert_eq!(
1043            tx.write_channel("dist", &[0], &[1.0]),
1044            Err(StoreError::NotSourceChannel("dist".into()))
1045        );
1046    }
1047
1048    #[test]
1049    fn slots_created_and_removed_in_one_transaction_are_reused() {
1050        let mut m = quad();
1051        let mut tx = m.begin_edit();
1052        let v = tx.add_vertices(&[[0.; 3]]).unwrap()[0];
1053        tx.remove_vertices(&[v]).unwrap();
1054        let patch = tx.commit().unwrap();
1055        assert!(patch.is_empty());
1056        assert_eq!(m.free_vertex_slots(), &[v]);
1057        let mut tx = m.begin_edit();
1058        assert_eq!(tx.add_vertices(&[[1.; 3]]).unwrap(), vec![v]);
1059    }
1060
1061    #[test]
1062    fn undoing_an_append_past_a_bitset_word_kills_the_slot() {
1063        // 64 vertices fill exactly one liveness word; vertex 64 starts a new word.
1064        let positions = vec![0.0f32; 64 * 3];
1065        let mut m = Mesh::from_buffers(&positions, &[]).unwrap();
1066        let mut tx = m.begin_edit();
1067        let v = tx.add_vertices(&[[1.; 3]]).unwrap()[0];
1068        let t = tx.add_triangles(&[[0, 1, v]]).unwrap()[0];
1069        let patch = tx.commit().unwrap();
1070        assert_eq!(v, 64);
1071        patch.revert(&mut m).unwrap();
1072        assert!(!m.is_vertex_alive(64));
1073        assert!(!m.is_triangle_alive(t));
1074        assert_eq!(m.triangles()[t as usize], [0, 0, 0]);
1075        assert_eq!(m.vertex_count(), 64);
1076        patch.apply(&mut m).unwrap();
1077        assert!(m.is_vertex_alive(64));
1078        assert_eq!(m.triangle(t), Some([0, 1, 64]));
1079    }
1080
1081    #[test]
1082    fn patches_only_apply_at_the_matching_revision() {
1083        let mut m = quad();
1084        let mut tx = m.begin_edit();
1085        tx.set_positions(&[0], &[[1.; 3]]).unwrap();
1086        let first = tx.commit().unwrap();
1087        let mut tx = m.begin_edit();
1088        tx.set_positions(&[0], &[[2.; 3]]).unwrap();
1089        let _second = tx.commit().unwrap();
1090        assert_eq!(first.revert(&mut m).err(), Some(StoreError::PatchMismatch));
1091        let mut other = quad();
1092        assert_eq!(
1093            first.apply(&mut other).err(),
1094            Some(StoreError::PatchMismatch)
1095        );
1096    }
1097}