Skip to main content

molframe_core/structure/
edit.rs

1//! Transactional coordinate editing over immutable snapshots.
2
3use super::{CoordinateStore, Structure, StructureData, validate};
4use crate::diagnostic::{Code, Diagnostic};
5use crate::index::ModelIndex;
6use crate::{ExecutionContext, MemoryBudgetError};
7
8/// A private coordinate copy that becomes a new structure only on commit.
9///
10/// The original structure remains immutable and every view into it stays valid.
11/// Dropping an editor discards the transaction.
12#[derive(Debug)]
13pub struct CoordinateEditor {
14    base: Structure,
15    coords: CoordinateStore,
16}
17
18impl Structure {
19    /// Starts a scoped coordinate edit under the shared execution account.
20    ///
21    /// Coordinate storage is copied for the new snapshot; topology and atom
22    /// annotations remain untouched. Call [`CoordinateEditor::commit`] to
23    /// validate and publish the new snapshot.
24    /// Shared topology remains borrowed. Direct coordinate frames detach under
25    /// the supplied account, and their reservations follow the copied backing
26    /// buffers through committed snapshots and clones.
27    ///
28    /// # Errors
29    ///
30    /// Returns before publishing an editor when all coordinate copies cannot fit.
31    pub fn edit_coordinates(
32        &self,
33        context: &ExecutionContext,
34    ) -> Result<CoordinateEditor, MemoryBudgetError> {
35        let mut coords = self.data().coords.clone();
36        coords.make_unique_in(context)?;
37        Ok(CoordinateEditor {
38            base: self.clone(),
39            coords,
40        })
41    }
42}
43
44impl CoordinateEditor {
45    /// Mutable positions of one dense model.
46    ///
47    /// Ragged models are independent structures and must be edited through the
48    /// corresponding value returned by [`Structure::ragged_models`].
49    pub fn positions_mut(&mut self, model: ModelIndex) -> Option<&mut [[f32; 3]]> {
50        match &mut self.coords {
51            CoordinateStore::Single(block) if model.get() == 0 => Some(block.as_mut_slice()),
52            CoordinateStore::Dense { frames } => frames
53                .get_mut(model.as_usize())
54                .map(crate::coords::CoordinateBlock::as_mut_slice),
55            CoordinateStore::Single(_) | CoordinateStore::Ragged { .. } => None,
56        }
57    }
58
59    /// Mutable positions of one dense model, with a diagnostic on failure.
60    ///
61    /// This is the fallible form for callers that must preserve molframe's
62    /// diagnostic contract across an FFI boundary.
63    ///
64    /// # Errors
65    ///
66    /// Returns `E6003` when `model` is not a dense frame of this structure.
67    pub fn try_positions_mut(&mut self, model: ModelIndex) -> Result<&mut [[f32; 3]], Diagnostic> {
68        self.positions_mut(model)
69            .ok_or_else(|| Diagnostic::new(Code::E6003))
70    }
71
72    /// Validates the transaction and returns a new immutable snapshot.
73    ///
74    /// # Errors
75    ///
76    /// Returns every violated structural invariant. The original snapshot is
77    /// unaffected whether the commit succeeds or fails.
78    pub fn commit(self) -> Result<Structure, Vec<crate::diagnostic::Diagnostic>> {
79        let mut data: StructureData = self.base.data().clone();
80        data.coords = self.coords;
81        finish(data)
82    }
83
84    /// Builds a validated snapshot that shares the editor's coordinate buffer.
85    ///
86    /// Nothing is copied, so the snapshot and the editor see the same
87    /// coordinates: use it only when the editor is not written to afterwards.
88    /// A caller that keeps a writable view of the buffer must use
89    /// [`Self::snapshot_detached`].
90    ///
91    /// # Errors
92    ///
93    /// Returns every violated structural invariant.
94    pub fn snapshot(&self) -> Result<Structure, Vec<crate::diagnostic::Diagnostic>> {
95        let mut data: StructureData = self.base.data().clone();
96        data.coords = self.coords.clone();
97        finish(data)
98    }
99}
100
101impl CoordinateEditor {
102    /// Builds a validated snapshot with a coordinate buffer of its own.
103    ///
104    /// The copy is charged to `context`'s memory budget before anything is
105    /// allocated. Writing to the editor, or to a foreign view of its buffer,
106    /// afterwards cannot change the published structure.
107    ///
108    /// # Errors
109    ///
110    /// Returns a memory diagnostic when the copy does not fit the budget, or
111    /// every violated structural invariant.
112    pub fn snapshot_detached(
113        &self,
114        context: &ExecutionContext,
115    ) -> Result<Structure, Vec<crate::diagnostic::Diagnostic>> {
116        let mut coords = self.coords.clone();
117        coords
118            .make_unique_in(context)
119            .map_err(|error| vec![crate::diagnostic::Diagnostic::from(error)])?;
120        let mut data: StructureData = self.base.data().clone();
121        data.coords = coords;
122        finish(data)
123    }
124}
125
126fn finish(mut data: StructureData) -> Result<Structure, Vec<crate::diagnostic::Diagnostic>> {
127    let Some(generation) = data.generation.next() else {
128        return Err(vec![
129            Diagnostic::new(Code::E6003).with_context("coordinate_generation", "exhausted"),
130        ]);
131    };
132    data.generation = generation;
133    let findings = validate(&data);
134    if findings.is_empty() {
135        Ok(Structure::new(data))
136    } else {
137        Err(findings)
138    }
139}
140
141#[cfg(test)]
142#[path = "edit_tests.rs"]
143mod tests;