1use std::collections::{BTreeMap, BTreeSet};
11
12use crate::completion::{PlannedProgress, Progress};
13use crate::error::{AxCode, AxError};
14use crate::node_id::NodeId;
15use crate::share::{self, Share};
16use crate::spine::{EvidenceCell, RoadmapRow, RoadmapStatus};
17
18use super::PlanTree;
19use super::node::{Held, PlanNode};
20
21impl PlanTree {
22 pub fn build(rows: Vec<RoadmapRow>) -> Result<PlanTree, AxError> {
32 let mut nodes: BTreeMap<NodeId, PlanNode> = BTreeMap::new();
33 for row in rows {
34 let id = row.id.clone();
35 if nodes.contains_key(&id) {
36 return Err(super::blocking::refusal(
37 "place a plan node",
38 format!("index {id} appears twice"),
39 "give every row its own index; two rows with one number are two plans",
40 ));
41 }
42 nodes.insert(
43 id,
44 PlanNode {
45 row,
46 share: Share::NONE,
47 children: Vec::new(),
48 },
49 );
50 }
51 let placed: Vec<NodeId> = nodes.keys().cloned().collect();
52 for id in &placed {
53 if let Some(parent) = id.parent() {
54 let Some(held) = nodes.get_mut(&parent) else {
55 return Err(super::blocking::refusal(
56 "place a plan node",
57 format!("{id} hangs under {parent}, which the table does not carry"),
58 "write the parent row first; a plan cannot branch from nothing",
59 ));
60 };
61 held.children.push(id.clone());
62 }
63 }
64 Self::check_dependencies(&nodes)?;
65 Self::check_branch_claims(&nodes)?;
66 let mut tree = PlanTree { nodes };
67 tree.divide()?;
68 Ok(tree)
69 }
70
71 fn check_dependencies(nodes: &BTreeMap<NodeId, PlanNode>) -> Result<(), AxError> {
74 for (id, node) in nodes {
75 for need in &node.row.needs {
76 if !nodes.contains_key(need) {
77 return Err(super::blocking::refusal(
78 "read a plan dependency",
79 format!("{id} needs {need}, which the table does not carry"),
80 "name a row the plan holds, or drop the dependency",
81 ));
82 }
83 if need == id {
84 return Err(super::blocking::refusal(
85 "read a plan dependency",
86 format!("{id} needs itself"),
87 "a node cannot wait for its own result",
88 ));
89 }
90 }
91 }
92 if let Some(circle) = super::blocking::first_cycle(nodes) {
93 let drawn: Vec<String> = circle.iter().map(NodeId::to_string).collect();
94 return Err(super::blocking::refusal(
95 "read a plan dependency",
96 format!("{} runs in a circle", drawn.join(" → ")),
97 "drop one of those dependencies; nothing in a circle can ever start",
98 ));
99 }
100 Ok(())
101 }
102
103 fn check_branch_claims(nodes: &BTreeMap<NodeId, PlanNode>) -> Result<(), AxError> {
107 for (id, node) in nodes {
108 if node.children.is_empty() || node.row.status != RoadmapStatus::Done {
109 continue;
110 }
111 for child in &node.children {
112 let unfinished = nodes
113 .get(child)
114 .is_some_and(|held| held.row.status != RoadmapStatus::Done);
115 if unfinished {
116 return Err(super::blocking::refusal(
117 "read a plan branch",
118 format!("{id} says done while {child} does not"),
119 "a branch is done when its children are; finish the child or reopen the \
120 branch",
121 ));
122 }
123 }
124 }
125 Ok(())
126 }
127
128 fn divide(&mut self) -> Result<(), AxError> {
130 let roots: Vec<NodeId> = self
131 .nodes
132 .keys()
133 .filter(|id| id.parent().is_none())
134 .cloned()
135 .collect();
136 super::share::hand_out(self, Share::WHOLE, &roots)?;
137 Ok(())
138 }
139
140 #[must_use]
141 pub fn get(&self, id: &NodeId) -> Option<&PlanNode> {
142 self.nodes.get(id)
143 }
144
145 pub fn nodes(&self) -> impl Iterator<Item = &PlanNode> {
147 self.nodes.values()
148 }
149
150 #[must_use]
151 pub fn len(&self) -> usize {
152 self.nodes.len()
153 }
154
155 #[must_use]
156 pub fn is_empty(&self) -> bool {
157 self.nodes.is_empty()
158 }
159
160 #[must_use]
165 pub fn needs_of(&self, id: &NodeId) -> BTreeSet<NodeId> {
166 let mut found = BTreeSet::new();
167 for step in id
168 .ancestors()
169 .into_iter()
170 .chain(std::iter::once(id.clone()))
171 {
172 if let Some(node) = self.nodes.get(&step) {
173 found.extend(node.row.needs.iter().cloned());
174 }
175 }
176 found
177 }
178
179 #[must_use]
186 pub fn ready(&self) -> Vec<NodeId> {
187 self.nodes
188 .values()
189 .filter(|node| node.is_leaf() && node.row.status == RoadmapStatus::NotStarted)
190 .filter(|node| {
191 self.needs_of(&node.row.id).iter().all(|need| {
192 self.nodes
193 .get(need)
194 .is_some_and(|held| held.row.status == RoadmapStatus::Done)
195 })
196 })
197 .map(|node| node.row.id.clone())
198 .collect()
199 }
200
201 pub fn claim(&self, id: &NodeId) -> Result<Held, AxError> {
210 let node = self.nodes.get(id).ok_or_else(|| {
211 super::blocking::refusal(
212 "claim a plan node",
213 format!("no row numbered {id}"),
214 format!("list the plan first; it carries {} rows", self.nodes.len()),
215 )
216 })?;
217 if !node.is_leaf() {
218 return Err(super::blocking::refusal(
219 "claim a plan node",
220 format!("{id} is a branch with {} children", node.children.len()),
221 "claim one of its children; a branch is done when its children are",
222 ));
223 }
224 if node.row.status != RoadmapStatus::NotStarted {
225 return Err(AxError::failure(
228 AxCode::GoalConflict,
229 "claim a plan node",
230 format!("{id} is `{}`", node.row.status.spelling()),
231 )
232 .with_recovery(self.somewhere_else()));
233 }
234 let waiting: Vec<String> = self
235 .needs_of(id)
236 .into_iter()
237 .filter(|need| {
238 self.nodes
239 .get(need)
240 .is_none_or(|held| held.row.status != RoadmapStatus::Done)
241 })
242 .map(|need| need.to_string())
243 .collect();
244 if !waiting.is_empty() {
245 return Err(super::blocking::refusal(
246 "claim a plan node",
247 format!("{id} waits for {}", waiting.join(", ")),
248 self.somewhere_else(),
249 ));
250 }
251 Ok(Held::of(id.clone()))
252 }
253
254 fn somewhere_else(&self) -> String {
256 self.ready().first().map_or_else(
257 || "nothing is ready; report to the User rather than picking a node".to_owned(),
258 |id| format!("claim {id}, which is ready"),
259 )
260 }
261
262 #[must_use]
271 pub fn progress(&self) -> Progress {
272 let mut done: u32 = 0;
273 let mut blocked: u32 = 0;
274 let mut total: u32 = 0;
275 let mut done_parts = Vec::new();
276 let mut blocked_parts = Vec::new();
277 for node in self.nodes.values() {
278 if !node.is_leaf() {
279 continue;
280 }
281 total = total.saturating_add(1);
282 match (&node.row.status, &node.row.evidence) {
283 (RoadmapStatus::Done, EvidenceCell::Present(_)) => {
284 done = done.saturating_add(1);
285 done_parts.push(node.share);
286 }
287 (RoadmapStatus::Blocked | RoadmapStatus::AwaitingApproval, _) => {
288 blocked = blocked.saturating_add(1);
289 blocked_parts.push(node.share);
290 }
291 _ => {}
292 }
293 }
294 Progress::Planned(PlannedProgress {
295 done,
296 blocked,
297 total,
298 done_ppb: share::gather(&done_parts).ppb(),
299 blocked_ppb: share::gather(&blocked_parts).ppb(),
300 })
301 }
302}
303
304#[cfg(test)]
305#[allow(
306 clippy::unwrap_used,
307 clippy::expect_used,
308 clippy::panic,
309 clippy::indexing_slicing,
310 clippy::arithmetic_side_effects,
311 reason = "test code"
312)]
313mod tests;