Skip to main content

blockworx_store/
worked.rs

1//! What an act touched: the scope its author was standing in and the
2//! entities its ops named.
3//!
4//! One derivation of "what did this change touch", consumed twice — the
5//! camera aims at it and the canvas rings it — so the two cannot disagree
6//! about where a change was. The names come from the manifest row the act
7//! wrote (`docs/log-vs-snapshot.md` §10.1), so nothing here has to guess
8//! which coordinate space a pin belongs to; a session with no rows reads
9//! its own commit instead.
10
11use blockworx_doc::block_model::{Area, Block, Image, Pin, Route, RouteLabel, Text};
12use blockworx_doc::commit::Commit;
13use blockworx_doc::document::Document;
14use blockworx_doc::id::{
15    AreaId, BlockId, EntityRef, ImageId, PinId, RouteId, RouteLabelId, TextId,
16};
17use blockworx_doc::opcode::OpCodes;
18
19/// What an act named and where it was standing — a manifest row's own two
20/// advisory fields, or the same pair read off a commit for a session that
21/// keeps no rows.
22///
23/// `scope` is the wire spelling of a scope: `BlockId::NULL` is the document
24/// root.
25#[derive(Clone, Debug, PartialEq)]
26pub struct Worked {
27    pub scope: BlockId,
28    pub touched: Vec<EntityRef>,
29}
30
31impl Worked {
32    /// An act with nothing to frame — a document's own past, seeded
33    /// rather than made here.
34    pub fn nothing() -> Self {
35        Self {
36            scope: BlockId::NULL,
37            touched: Vec::new(),
38        }
39    }
40
41    /// What the row says. The scope is the author's own, so a pin that
42    /// shows on its owner's outside needs no special case here.
43    pub fn recorded(row: &crate::manifest::Row) -> Self {
44        Self {
45            scope: row.scope.innermost().unwrap_or(BlockId::NULL),
46            touched: row.touched.clone(),
47        }
48    }
49
50    /// What a commit says, for a session with no row to read: the
51    /// entities its ops name, and the scope the first of them sits in.
52    pub fn of(document: &Document, commit: &Commit) -> Self {
53        let mut touched: Vec<EntityRef> = Vec::new();
54        for target in commit.ops().iter().map(OpCodes::target) {
55            if !touched.contains(&target) {
56                touched.push(target);
57            }
58        }
59        let scope = touched
60            .iter()
61            .find_map(|subject| {
62                scope_of(
63                    Step {
64                        before: document,
65                        after: document,
66                    },
67                    *subject,
68                )
69            })
70            .unwrap_or(BlockId::NULL);
71        Self { scope, touched }
72    }
73}
74
75/// The two documents a commit stands between. A subject is read from
76/// `before` — a step frames where the thing *was*, which is what the camera
77/// has to reach before the change is drawn — and from `after` only where the
78/// commit itself brought it into being. Between them they hold every subject
79/// a commit can name, which is what a delete's footprint needs now that a
80/// delete removes rather than tombstones.
81#[derive(Clone, Copy)]
82pub struct Step<'a> {
83    pub before: &'a Document,
84    pub after: &'a Document,
85}
86
87impl<'a> Step<'a> {
88    pub fn block(self, id: BlockId) -> Option<&'a Block> {
89        self.before.block(&id).or_else(|| self.after.block(&id))
90    }
91    pub fn pin(self, id: PinId) -> Option<&'a Pin> {
92        self.before.pin(&id).or_else(|| self.after.pin(&id))
93    }
94    pub fn route(self, id: RouteId) -> Option<&'a Route> {
95        self.before.route(&id).or_else(|| self.after.route(&id))
96    }
97    pub fn route_label(self, id: RouteLabelId) -> Option<&'a RouteLabel> {
98        self.before
99            .route_label(&id)
100            .or_else(|| self.after.route_label(&id))
101    }
102    pub fn text(self, id: TextId) -> Option<&'a Text> {
103        self.before.text(&id).or_else(|| self.after.text(&id))
104    }
105    pub fn area(self, id: AreaId) -> Option<&'a Area> {
106        self.before.area(&id).or_else(|| self.after.area(&id))
107    }
108    pub fn image(self, id: ImageId) -> Option<&'a Image> {
109        self.before.image(&id).or_else(|| self.after.image(&id))
110    }
111}
112
113/// The scope a subject is drawn in, read across a step, in the wire
114/// spelling. A pin is the exception: it shows at its slot anchor, which is
115/// on its owner's *outside*, so it belongs to the scope the owner is a
116/// child of.
117pub fn scope_of(step: Step<'_>, subject: EntityRef) -> Option<BlockId> {
118    let inside = container(step, subject)?;
119    Some(match subject {
120        EntityRef::Pin(_) => parent_of(step, inside),
121        _ => inside,
122    })
123}
124
125/// The block a subject sits inside: a shape's owner, a block's parent, a
126/// wire label's wire's owner. The document itself and an asset sit in
127/// nothing.
128fn container(step: Step<'_>, subject: EntityRef) -> Option<BlockId> {
129    Some(match subject {
130        EntityRef::Block(id) => step.block(id)?.parent,
131        EntityRef::Area(id) => step.area(id)?.owner,
132        EntityRef::Text(id) => step.text(id)?.owner,
133        EntityRef::Image(id) => step.image(id)?.owner,
134        EntityRef::Route(id) => step.route(id)?.owner,
135        EntityRef::RouteLabel(id) => {
136            let route = step.route_label(id)?.owner;
137            step.route(route)?.owner
138        }
139        EntityRef::Pin(id) => step.pin(id)?.owner,
140        EntityRef::Document | EntityRef::Asset(_) => return None,
141    })
142}
143
144/// The scope a block is a child of. The root's own parent is the root.
145fn parent_of(step: Step<'_>, block: BlockId) -> BlockId {
146    step.block(block)
147        .map_or(BlockId::NULL, |block| block.parent)
148}