Skip to main content

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}