Skip to main content

blockworx/document/
model.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;
14use crate::presentation::store::{IdMap, IdMapExt};
15use crate::store::RectId;
16
17/// Identifies one *value* of the block map. A fresh stamp is minted process-wide
18/// on every mutable access, and a clone carries its source's stamp, so equal
19/// generations imply identical content. Caches derived from the document (the
20/// spatial index) compare it instead of relying on callers to invalidate them.
21#[derive(Clone, Copy, PartialEq, Eq, Debug)]
22pub struct Generation(u64);
23
24impl Generation {
25    fn next() -> Self {
26        static NEXT: AtomicU64 = AtomicU64::new(0);
27        Self(NEXT.fetch_add(1, Ordering::Relaxed))
28    }
29}
30
31/// The document's block map. Handing out `&mut` — the only way to reach any
32/// block, and therefore any shape or route — is what stamps a new
33/// [`Generation`], so a mutation cannot fail to be observable to a cache.
34#[derive(Clone, Debug)]
35pub struct Blocks {
36    map: IdMap<RectId, Block>,
37    generation: Generation,
38}
39
40impl Blocks {
41    pub fn generation(&self) -> Generation {
42        self.generation
43    }
44}
45
46/// Content equality only: two maps holding the same blocks are equal whether or
47/// not they were reached through a mutable borrow (undo history compares states
48/// for change, and a borrow that changed nothing is not a change).
49impl PartialEq for Blocks {
50    fn eq(&self, other: &Self) -> bool {
51        self.map == other.map
52    }
53}
54
55impl Default for Blocks {
56    fn default() -> Self {
57        IdMap::default().into()
58    }
59}
60
61impl From<IdMap<RectId, Block>> for Blocks {
62    fn from(map: IdMap<RectId, Block>) -> Self {
63        Self {
64            map,
65            generation: Generation::next(),
66        }
67    }
68}
69
70impl Deref for Blocks {
71    type Target = IdMap<RectId, Block>;
72    fn deref(&self) -> &Self::Target {
73        &self.map
74    }
75}
76
77impl DerefMut for Blocks {
78    fn deref_mut(&mut self) -> &mut Self::Target {
79        self.generation = Generation::next();
80        &mut self.map
81    }
82}
83
84/// The whole document: a flat, globally-keyed map of blocks plus the root id.
85#[derive(Clone, Debug, PartialEq)]
86pub struct Document {
87    /// Display name, when the document carries one of its own. `None` falls back
88    /// to the name of the directory or file holding it. Part of the document (and
89    /// so of the undo state) because a rename is an edit, not a view setting.
90    pub name: Option<String>,
91    /// Id of the root (top) block within [`Self::blocks`].
92    pub top_id: RectId,
93    /// Every block in the document, keyed by its globally-unique id. Parent →
94    /// child links are recorded in each block's [`Block::children`] set, not by
95    /// nesting.
96    pub blocks: Blocks,
97}
98
99impl Default for Document {
100    fn default() -> Self {
101        // One empty "top" block, as the old single-`Block` default was.
102        let mut blocks = Blocks::default();
103        let top_id = blocks.insert_value(Block::default());
104        Self {
105            name: None,
106            top_id,
107            blocks,
108        }
109    }
110}
111
112impl Document {
113    /// The stamp of the current block map (see [`Generation`]).
114    pub fn generation(&self) -> Generation {
115        self.blocks.generation()
116    }
117
118    /// Insert `block` into the flat map under a fresh document-global id and
119    /// record it as a child of `parent` (appended after `parent`'s existing
120    /// children), returning the new id. The parent link is skipped if `parent`
121    /// is not present.
122    pub fn add_child(&mut self, parent: RectId, block: Block) -> RectId {
123        let id = self.blocks.insert_value(block);
124        if let Some(p) = self.blocks.get_mut(&parent) {
125            p.children.insert(id);
126        }
127        id
128    }
129
130    pub fn block(&self, id: RectId) -> Option<&Block> {
131        self.blocks.get(&id)
132    }
133    pub fn block_mut(&mut self, id: RectId) -> Option<&mut Block> {
134        self.blocks.get_mut(&id)
135    }
136
137    /// Iterate block `id`'s children (id + `Block`) in stored order, resolving
138    /// each child id through the flat map and skipping any dangling id (so a
139    /// hand-edited document degrades gracefully).
140    pub fn child_blocks(&self, id: RectId) -> impl Iterator<Item = (RectId, &Block)> + '_ {
141        self.blocks.get(&id).into_iter().flat_map(move |b| {
142            b.children
143                .iter()
144                .filter_map(move |&cid| self.blocks.get(&cid).map(|cb| (cid, cb)))
145        })
146    }
147
148    /// All transitive descendant block ids of `id` (excluding `id` itself). Used
149    /// to remove or copy a whole subtree from the flat map.
150    pub fn descendants(&self, id: RectId) -> Vec<RectId> {
151        let mut out = Vec::new();
152        let mut stack: Vec<RectId> = self
153            .blocks
154            .get(&id)
155            .map(|b| b.children.iter().copied().collect())
156            .unwrap_or_default();
157        while let Some(cid) = stack.pop() {
158            out.push(cid);
159            if let Some(b) = self.blocks.get(&cid) {
160                stack.extend(b.children.iter().copied());
161            }
162        }
163        out
164    }
165}