Skip to main content

blockworx/document_ng/
document.rs

1//! The flat document model.
2//!
3//! A [`Document`] is a single map of every block keyed by a globally-unique
4//! [`RectId`], plus the [`top_id`](Document::top_id) of the root block. It
5//! replaces the old nested `Block` tree: a [`Block`] now records its children
6//! only by id (see [`Block::children`]), and those children live here in
7//! [`Document::blocks`]. The on-disk form is therefore flat and hand-editable —
8//! every block is a top-level entry under its id, listing its children by id.
9
10use std::ops::{Deref, DerefMut};
11use std::sync::atomic::{AtomicU64, Ordering};
12
13use super::{Block, BlockPath};
14use crate::store::{IdMap, IdMapExt, RectId};
15
16/// Identifies one *value* of the block map. A fresh stamp is minted process-wide
17/// on every mutable access, and a clone carries its source's stamp, so equal
18/// generations imply identical content. Caches derived from the document (the
19/// spatial index) compare it instead of relying on callers to invalidate them.
20#[derive(Clone, Copy, PartialEq, Eq, Debug)]
21pub struct Generation(u64);
22
23impl Generation {
24    fn next() -> Self {
25        static NEXT: AtomicU64 = AtomicU64::new(0);
26        Self(NEXT.fetch_add(1, Ordering::Relaxed))
27    }
28}
29
30/// The document's block map. Handing out `&mut` — the only way to reach any
31/// block, and therefore any shape or route — is what stamps a new
32/// [`Generation`], so a mutation cannot fail to be observable to a cache.
33#[derive(Clone, Debug)]
34pub struct Blocks {
35    map: IdMap<RectId, Block>,
36    generation: Generation,
37}
38
39impl Blocks {
40    pub fn generation(&self) -> Generation {
41        self.generation
42    }
43}
44
45/// Content equality only: two maps holding the same blocks are equal whether or
46/// not they were reached through a mutable borrow (undo history compares states
47/// for change, and a borrow that changed nothing is not a change).
48impl PartialEq for Blocks {
49    fn eq(&self, other: &Self) -> bool {
50        self.map == other.map
51    }
52}
53
54impl Default for Blocks {
55    fn default() -> Self {
56        IdMap::default().into()
57    }
58}
59
60impl From<IdMap<RectId, Block>> for Blocks {
61    fn from(map: IdMap<RectId, Block>) -> Self {
62        Self {
63            map,
64            generation: Generation::next(),
65        }
66    }
67}
68
69impl Deref for Blocks {
70    type Target = IdMap<RectId, Block>;
71    fn deref(&self) -> &Self::Target {
72        &self.map
73    }
74}
75
76impl DerefMut for Blocks {
77    fn deref_mut(&mut self) -> &mut Self::Target {
78        self.generation = Generation::next();
79        &mut self.map
80    }
81}
82
83/// The whole document: a flat, globally-keyed map of blocks plus the root id.
84#[derive(Clone, Debug, PartialEq)]
85pub struct Document {
86    /// Id of the root (top) block within [`Self::blocks`].
87    pub top_id: RectId,
88    /// Every block in the document, keyed by its globally-unique id. Parent →
89    /// child links are recorded in each block's [`Block::children`] set, not by
90    /// nesting.
91    pub blocks: Blocks,
92}
93
94impl Default for Document {
95    fn default() -> Self {
96        // One empty "top" block, as the old single-`Block` default was.
97        let mut blocks = Blocks::default();
98        let top_id = blocks.insert_value(Block::default());
99        Self { top_id, blocks }
100    }
101}
102
103impl Document {
104    /// The stamp of the current block map (see [`Generation`]).
105    pub fn generation(&self) -> Generation {
106        self.blocks.generation()
107    }
108
109    /// Insert `block` into the flat map under a fresh document-global id and
110    /// record it as a child of `parent` (appended after `parent`'s existing
111    /// children), returning the new id. The parent link is skipped if `parent`
112    /// is not present.
113    pub fn add_child(&mut self, parent: RectId, block: Block) -> RectId {
114        let id = self.blocks.insert_value(block);
115        if let Some(p) = self.blocks.get_mut(&parent) {
116            p.children.insert(id);
117        }
118        id
119    }
120
121    pub fn block(&self, id: RectId) -> Option<&Block> {
122        self.blocks.get(&id)
123    }
124    pub fn block_mut(&mut self, id: RectId) -> Option<&mut Block> {
125        self.blocks.get_mut(&id)
126    }
127
128    /// The id of the block a `path` resolves to: the path's last segment, or the
129    /// document root when the path is empty. With globally-unique ids the last
130    /// segment alone identifies the block; the rest of the path is retained so the
131    /// navigation control can show (and jump to) each ancestor.
132    pub fn current_id(&self, path: &BlockPath) -> RectId {
133        path.segments()
134            .last()
135            .copied()
136            .filter(|id| self.blocks.contains_key(id))
137            .unwrap_or(self.top_id)
138    }
139
140    /// Iterate block `id`'s children (id + `Block`) in stored order, resolving
141    /// each child id through the flat map and skipping any dangling id (so a
142    /// hand-edited document degrades gracefully).
143    pub fn child_blocks(&self, id: RectId) -> impl Iterator<Item = (RectId, &Block)> + '_ {
144        self.blocks.get(&id).into_iter().flat_map(move |b| {
145            b.children
146                .iter()
147                .filter_map(move |&cid| self.blocks.get(&cid).map(|cb| (cid, cb)))
148        })
149    }
150
151    /// The navigation path whose *current* diagram contains `target` as a direct
152    /// child: the ancestor chain from the top block's child down to `target`'s
153    /// parent. Returns an empty path when `target` is a direct child of the top
154    /// block, and `None` when `target` is the top block itself or is absent. Used
155    /// by the navigation tree to jump the canvas to any block's own level before
156    /// selecting it.
157    pub fn path_to_parent(&self, target: RectId) -> Option<BlockPath> {
158        fn descend(doc: &Document, node: RectId, target: RectId, trail: &mut Vec<RectId>) -> bool {
159            let Some(block) = doc.blocks.get(&node) else {
160                return false;
161            };
162            for &cid in &block.children {
163                if cid == target {
164                    return true;
165                }
166                trail.push(cid);
167                if descend(doc, cid, target, trail) {
168                    return true;
169                }
170                trail.pop();
171            }
172            false
173        }
174        let mut trail = Vec::new();
175        descend(self, self.top_id, target, &mut trail).then(|| {
176            let mut path = BlockPath::empty();
177            for id in trail {
178                path.push(id);
179            }
180            path
181        })
182    }
183
184    #[cfg(test)]
185    fn add_named(&mut self, parent: RectId) -> RectId {
186        use crate::document_ng::Block;
187        self.add_child(
188            parent,
189            Block::new(
190                String::new(),
191                egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
192            ),
193        )
194    }
195
196    /// All transitive descendant block ids of `id` (excluding `id` itself). Used
197    /// to remove or copy a whole subtree from the flat map.
198    pub fn descendants(&self, id: RectId) -> Vec<RectId> {
199        let mut out = Vec::new();
200        let mut stack: Vec<RectId> = self
201            .blocks
202            .get(&id)
203            .map(|b| b.children.iter().copied().collect())
204            .unwrap_or_default();
205        while let Some(cid) = stack.pop() {
206            out.push(cid);
207            if let Some(b) = self.blocks.get(&cid) {
208                stack.extend(b.children.iter().copied());
209            }
210        }
211        out
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn path_to_parent_walks_from_the_top_child_down_to_the_parent() {
221        let mut doc = Document::default();
222        let top = doc.top_id;
223        let a = doc.add_named(top); // child of top
224        let b = doc.add_named(a); // grandchild
225        let c = doc.add_named(b); // great-grandchild
226        let sib = doc.add_named(top); // another top child
227
228        // A direct child of the top block needs no descent.
229        assert_eq!(doc.path_to_parent(a), Some(BlockPath::empty()));
230        assert_eq!(doc.path_to_parent(sib), Some(BlockPath::empty()));
231
232        // Deeper blocks resolve to the chain that makes their parent current.
233        let mut want_b = BlockPath::empty();
234        want_b.push(a);
235        assert_eq!(doc.path_to_parent(b), Some(want_b));
236
237        let mut want_c = BlockPath::empty();
238        want_c.push(a);
239        want_c.push(b);
240        assert_eq!(doc.path_to_parent(c), Some(want_c));
241
242        // The top block itself has no parent, and unknown ids are absent.
243        assert_eq!(doc.path_to_parent(top), None);
244        assert_eq!(doc.path_to_parent(RectId::nth_default(999)), None);
245    }
246}