Evaluation: under each_feature a feature's kept bodies are checked once

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

The model carries the mode (Model::set_validation) and runs each feature
under it. When a step left its check, every body the feature made or
changed is checked by SMLib before the state is kept: a body the check
refuses fails the feature with the check's reason and the body's name.
The mode is part of the cache context, except each_step, whose results
are develop's.
This commit is contained in:
jyviko
2026-10-30 09:36:05 -04:00
committed by jyviko
parent 4030b795c4
commit 9acbcf924b
+49 -3
View File
@@ -1032,6 +1032,8 @@ pub struct Model {
primed: Option<(u64, Arc<std::collections::HashMap<u64, FeatureResult>>)>,
/// Shared with copies of this evaluator; states hold only a weak reference.
frame_bodies: Arc<std::sync::Mutex<FrameBodies>>,
/// When SMLib's check runs on the features this model evaluates ([`Model::set_validation`]).
validation: kernel::Validation,
}
/// Parameters a feature uses directly (inputs, sketch dimensions and the expressions of a
@@ -1361,6 +1363,33 @@ fn context_digest(doc: &Document, vals: &BTreeMap<String, Value>) -> u64 {
h.finish()
}
/// What the validation mode adds to the context digest: nothing for checking every step (how
/// every evaluation before the mode checked, so its results and saved caches stand), so a result
/// is taken from the cache only under the mode it was evaluated in.
fn validation_digest(validation: kernel::Validation) -> u64 {
match validation {
kernel::Validation::EachStep => 0,
v => {
let mut h = std::collections::hash_map::DefaultHasher::new();
("validation", v.id()).hash(&mut h);
h.finish()
}
}
}
/// SMLib's check of every body a feature made or changed (in `after`, not in `before`, the
/// state it started from), once its steps left their checks to its end: the first that fails is
/// the feature's error, naming the body.
fn check_kept(before: &ModelState, after: &ModelState) -> kernel::Result<()> {
for b in &after.bodies {
if before.bodies.iter().any(|old| old.body.same_shape(&b.body)) {
continue;
}
kernel::check_kept(&b.body).map_err(|e| kernel::KernelError::Failed(format!("body `{}`: {e}", b.name)))?;
}
Ok(())
}
/// Whether `after`, a state feature `f` evaluated to from `before` in an earlier evaluation, is
/// still its result in `doc`: a feature saved before format 14 names the pieces it splits a body
/// into as the later features pick them ([`crate::lineage::order_legacy_lumps`]), which neither
@@ -1846,6 +1875,19 @@ impl Model {
self.primed = None;
}
/// When SMLib's check runs on the features this model evaluates.
pub fn validation(&self) -> kernel::Validation {
self.validation
}
/// Check the features evaluated from now on under `validation` ([`kernel::Validation`]):
/// after every sweep, boolean and blend, once on the bodies each feature keeps, or not at
/// all. The results already there stand until their features are evaluated again; the cache
/// keeps results apart by mode, so one evaluated under another mode is not taken up.
pub fn set_validation(&mut self, validation: kernel::Validation) {
self.validation = validation;
}
/// Re-evaluate the document, reusing results up to the first changed feature.
pub fn evaluate(&mut self, doc: &Document) {
let _span = solidcraft_profile::span("doc", "evaluate");
@@ -1862,7 +1904,8 @@ impl Model {
let mut recomputed = 0;
let mut restored = 0;
let marker = doc.marker.unwrap_or(usize::MAX);
let ctx = context_digest(doc, &vals);
let validation = self.validation;
let ctx = context_digest(doc, &vals) ^ validation_digest(validation);
let previous_context = self.context;
let mut rolled_results = std::mem::take(&mut self.rolled_results);
self.context = ctx;
@@ -2029,8 +2072,11 @@ impl Model {
let r = match frame_error.or(bad_param) {
Some(e) => Err(kernel::KernelError::Failed(e)),
None => kernel::guard(&f.name, || {
eval_feature(doc, &vals, f, &mut next, &mut warning, &mut import_report, &mut repair)
.map_err(|e| kernel::KernelError::Failed(e.to_string()))
let (r, deferred) =
kernel::validating(validation, || eval_feature(doc, &vals, f, &mut next, &mut warning, &mut import_report, &mut repair));
r.map_err(|e| kernel::KernelError::Failed(e.to_string()))?;
// The checks the feature's steps left to its end: the bodies it keeps.
if deferred { check_kept(&state, &next) } else { Ok(()) }
}),
};
if r.is_ok() {