geop_ops/program/mod.rs
1//! [`Program`]: an ordered list of operations that builds a [`Part`], and
2//! [`ProgramRunner`], which builds it incrementally.
3//!
4//! Both are generic over the set of operations the program can use (see
5//! [`Operations`]): which operations those are is for an application to
6//! decide.
7
8use std::collections::HashSet;
9
10use geop_core_math::{
11 geop_error::{GeopError, GeopResult, WithContext},
12 scalars::Scalar,
13 with_context,
14};
15use serde::{Deserialize, Serialize};
16
17use crate::{Part, operation::Operations, validate_operation_id};
18
19/// One step of a [`Program`]: an operation with its arguments, and the id
20/// everything it creates is named after. Serializes as
21/// `{"id": "box", "operation": "extrude", "args": {...}}`.
22#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
23pub struct Step<O> {
24 pub id: String,
25 #[serde(flatten)]
26 pub operation: O,
27}
28
29/// A recipe for building a [`Part`]: an ordered list of steps, each referring
30/// to what earlier ones built only by name. Those names come from step ids
31/// and sketch element ids, never from the internal ids a build happens to
32/// assign (see `geop_ops`), so a program means the same thing every
33/// time it is run — including after a round trip through JSON.
34#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
35pub struct Program<O> {
36 pub steps: Vec<Step<O>>,
37}
38
39impl<O> Default for Program<O> {
40 fn default() -> Self {
41 Self { steps: Vec::new() }
42 }
43}
44
45impl<O: Operations> Program<O> {
46 pub fn new() -> Self {
47 Self::default()
48 }
49
50 /// Appends the step `id`: `operation` with its arguments.
51 pub fn push(&mut self, id: impl Into<String>, operation: impl Into<O>) {
52 self.steps.push(Step {
53 id: id.into(),
54 operation: operation.into(),
55 });
56 }
57
58 /// The position of step `id`.
59 pub fn index_of(&self, id: &str) -> GeopResult<usize> {
60 self.steps
61 .iter()
62 .position(|s| s.id == id)
63 .ok_or_else(|| GeopError::new(format!("program has no step {id:?}")))
64 }
65
66 /// An id no step has yet, for a new step running `operation`: its
67 /// label, lowercased, and the lowest number that makes it unique —
68 /// `sketch1`, `extrude2`.
69 pub fn fresh_id(&self, operation: &O) -> String {
70 let base = operation.label().to_lowercase().replace(' ', "_");
71 (1..)
72 .map(|n| format!("{base}{n}"))
73 .find(|id| self.steps.iter().all(|s| &s.id != id))
74 .expect("some number is free")
75 }
76
77 /// Checks that every step id is a valid operation id and unique: every
78 /// name a step creates is built from its id.
79 pub fn validate(&self) -> GeopResult<()> {
80 let mut ids = HashSet::new();
81 for step in &self.steps {
82 validate_operation_id(&step.id)?;
83 if !ids.insert(step.id.as_str()) {
84 return Err(GeopError::new(format!(
85 "program has more than one step with id {:?}",
86 step.id
87 )));
88 }
89 }
90 Ok(())
91 }
92
93 /// Runs every step in order, starting from an empty part, and returns
94 /// the part the whole program builds — or the first error any step
95 /// raises, at which point the steps after it never run. A program
96 /// always starts from nothing: its names are only guaranteed to be
97 /// unique, and to mean the same thing on every run, when every entity
98 /// was created by one of its own steps.
99 ///
100 /// After each step, every entity of the part must have a name — an
101 /// operation that leaves one unnamed has broken the one guarantee a
102 /// program relies on.
103 pub fn build<S: Scalar>(&self) -> GeopResult<Part<S>> {
104 self.validate()?;
105 let mut part = Part::new();
106 for (index, step) in self.steps.iter().enumerate() {
107 part = run_step(part, index, step)?;
108 }
109 Ok(part)
110 }
111
112 /// The program as pretty-printed JSON: one step per object, every sketch
113 /// entity keyed by its id, so edits show up as small line diffs.
114 pub fn to_json(&self) -> GeopResult<String> {
115 serde_json::to_string_pretty(self)
116 .map_err(|e| GeopError::new(format!("serializing program: {e}")))
117 }
118
119 pub fn from_json(json: &str) -> GeopResult<Self> {
120 let program: Self = serde_json::from_str(json)
121 .map_err(|e| GeopError::new(format!("reading program: {e}")))?;
122 program.validate()?;
123 Ok(program)
124 }
125}
126
127/// Step `index` of a program applied to `part`, with every name checked.
128fn run_step<S: Scalar, O: Operations>(
129 part: Part<S>,
130 index: usize,
131 step: &Step<O>,
132) -> GeopResult<Part<S>> {
133 let ctx = with_context!("program step {index} ({:?})", step.id);
134 let part = step.operation.apply(part, &step.id).with_context(ctx)?;
135 part.check_names().with_context(ctx)?;
136 Ok(part)
137}
138
139/// How one step of a run went.
140#[derive(Clone, Debug, PartialEq, Serialize)]
141pub struct StepResult {
142 pub id: String,
143 /// Why the step failed; `None` if it succeeded.
144 pub error: Option<String>,
145}
146
147/// Builds a program the way an editor needs it built: incrementally, and
148/// only as far as asked.
149///
150/// It keeps the part after every step it has run. Running again after an
151/// edit reuses the part after the longest unchanged prefix of steps, so
152/// changing the last step replays one step, not the whole history. And a
153/// run can stop early — while a step in the middle is being edited, only
154/// the steps up to it need to run, however long the rest of the program is.
155/// Parts past the stop are kept, not discarded, so moving the stop back
156/// again costs nothing.
157///
158/// A run stops at the first step that fails: the steps after it would only
159/// fail too, for want of what it should have built.
160pub struct ProgramRunner<S: Scalar, O> {
161 /// The steps the cache was built from.
162 steps: Vec<Step<O>>,
163 /// `parts[i]`: the part after `steps[..i]`. A failed step leaves the
164 /// part as it was, so this stays one longer than `steps`.
165 parts: Vec<Part<S>>,
166 results: Vec<StepResult>,
167 /// How many steps the last run covers.
168 ran: usize,
169}
170
171impl<S: Scalar, O: Operations> ProgramRunner<S, O> {
172 pub fn new() -> Self {
173 Self {
174 steps: Vec::new(),
175 parts: vec![Part::new()],
176 results: Vec::new(),
177 ran: 0,
178 }
179 }
180
181 /// Runs the first `stop` steps of `program` — all of them if `None` —
182 /// reusing whatever the previous runs built that still applies. See
183 /// [`ProgramRunner::part`] and [`ProgramRunner::results`] for the
184 /// outcome.
185 pub fn run(&mut self, program: &Program<O>, stop: Option<usize>) {
186 let common = self
187 .steps
188 .iter()
189 .zip(&program.steps)
190 .take_while(|(a, b)| a == b)
191 .count();
192 self.steps.truncate(common);
193 self.parts.truncate(common + 1);
194 self.results.truncate(common);
195
196 let target = stop.unwrap_or(program.steps.len()).min(program.steps.len());
197 let failed = |results: &[StepResult]| results.iter().any(|r| r.error.is_some());
198 while self.steps.len() < target && !failed(&self.results) {
199 let index = self.steps.len();
200 let step = &program.steps[index];
201 let before = self.parts.last().expect("parts is never empty");
202 let (part, error) = match run_step(before.clone(), index, step) {
203 Ok(part) => (part, None),
204 Err(e) => (before.clone(), Some(e.to_string())),
205 };
206 self.steps.push(step.clone());
207 self.parts.push(part);
208 self.results.push(StepResult {
209 id: step.id.clone(),
210 error,
211 });
212 }
213 // Up to the stop, or up to and including the first failure.
214 let first_failure = self.results.iter().position(|r| r.error.is_some());
215 self.ran = match first_failure {
216 Some(f) if f < target => f + 1,
217 _ => target.min(self.steps.len()),
218 };
219 }
220
221 /// The part the last run built.
222 pub fn part(&self) -> &Part<S> {
223 &self.parts[self.ran]
224 }
225
226 /// The part the first `n` steps of the last run built — or, where the
227 /// run stopped before, the part it built.
228 pub fn part_at(&self, n: usize) -> &Part<S> {
229 &self.parts[n.min(self.ran)]
230 }
231
232 /// One result per step the last run covered.
233 pub fn results(&self) -> &[StepResult] {
234 &self.results[..self.ran]
235 }
236}
237
238impl<S: Scalar, O: Operations> Default for ProgramRunner<S, O> {
239 fn default() -> Self {
240 Self::new()
241 }
242}