diff --git a/crates/kernel/src/step_in/mod.rs b/crates/kernel/src/step_in/mod.rs index 49443180f..ba1d39545 100644 --- a/crates/kernel/src/step_in/mod.rs +++ b/crates/kernel/src/step_in/mod.rs @@ -764,27 +764,24 @@ impl Reader { let first = self.converted; // The next solids, across instances and parts, converted together: parts still to // orient are shared out between the workers. A part that comes back within the window - // waits for the next one, which places the orientation its first instance found. - let mut last = first + 1; - if !single { - let mut fresh: HashSet = self.plan.get(first).map(|p| p.item).into_iter().collect(); - while let Some(p) = self.plan.get(last) - && last - first < MAX_WINDOW - { - if !self.oriented.contains_key(&p.item) && !fresh.insert(p.item) { - break; - } - last += 1; - } - } + // is placed once its first instance is read, from the orientation that instance found. + let last = if single { first + 1 } else { (first + MAX_WINDOW).min(self.plan.len()) }; self.converted = last; let ex = self.ex.clone(); + // Each planned solid of the window, with its job (`None`: a later instance of a part an + // earlier job of the window reads, placed after it). let mut sources = Vec::new(); let mut jobs = Vec::new(); + let mut reading = HashSet::new(); for i in first..last { self.made.push(None); let Some(p) = self.plan.get(i) else { continue }; let (item, units) = (p.item, p.units); + let placement = self.batches.get(p.batch).map_or(IDENTITY, |b| b.m); + if reading.contains(&item) { + sources.push((i, item, placement, None)); + continue; + } if let Some(again) = self.unreadable.get(&item) { if let Some(msg) = again.clone() { let label = self.label(item); @@ -794,15 +791,42 @@ impl Reader { } let source = match self.oriented.get(&item) { Some(part) => parallel::Source::Oriented(part.clone()), - None => parallel::Source::Item { item, units }, + None => { + reading.insert(item); + parallel::Source::Item { item, units } + } }; - let placement = self.batches.get(p.batch).map_or(IDENTITY, |b| b.m); - sources.push((i, item)); + sources.push((i, item, placement, Some(jobs.len()))); jobs.push(parallel::Job { source, placement, check_mesh: !self.unmeshed_checked.contains(&item) }); } // Reading and orienting new parts is shared out between the workers. - let prepared = parallel::bodies(&ex, &jobs, self.workers); - for ((i, item), made) in sources.into_iter().zip(prepared) { + let mut prepared: Vec> = parallel::bodies(&ex, &jobs, self.workers).into_iter().map(Some).collect(); + for (i, item, placement, job) in sources { + let made = match job { + Some(j) => match prepared.get_mut(j).and_then(Option::take) { + Some(made) => made, + None => continue, + }, + // A later instance of a part read in this window: as it would be in the next + // window, after its first instance. + None => { + if let Some(again) = self.unreadable.get(&item) { + if let Some(msg) = again.clone() { + let label = self.label(item); + self.warn(format!("{label}: not imported: {msg}")); + } + continue; + } + let source = match self.oriented.get(&item) { + Some(part) => parallel::Source::Oriented(part.clone()), + None => match self.plan.get(i) { + Some(p) => parallel::Source::Item { item, units: p.units }, + None => continue, + }, + }; + parallel::prepare(&ex, ¶llel::Job { source, placement, check_mesh: !self.unmeshed_checked.contains(&item) }) + } + }; let Some(e) = ex.get(item) else { continue }; let Some(batch) = self.plan.get(i).and_then(|p| self.batches.get(p.batch)) else { continue }; let (m, path, product, instance) = (batch.m, batch.path.clone(), batch.product.clone(), batch.instance.clone()); diff --git a/crates/kernel/src/step_in/parallel.rs b/crates/kernel/src/step_in/parallel.rs index b29a42730..0761251a1 100644 --- a/crates/kernel/src/step_in/parallel.rs +++ b/crates/kernel/src/step_in/parallel.rs @@ -70,7 +70,7 @@ pub(super) fn workers() -> usize { } } -fn prepare(ex: &Exchange, job: &Job) -> Prepared { +pub(super) fn prepare(ex: &Exchange, job: &Job) -> Prepared { let _span = solidcraft_profile::span("import", "step.body"); let (part, read) = match &job.source { Source::Oriented(part) => (part.clone(), None), diff --git a/crates/kernel/src/step_in/tests.rs b/crates/kernel/src/step_in/tests.rs index 3481fa9ef..e7d78ea3e 100644 --- a/crates/kernel/src/step_in/tests.rs +++ b/crates/kernel/src/step_in/tests.rs @@ -772,9 +772,9 @@ fn instanced_parts_are_oriented_once() { } /// Distinct parts are oriented together, not one occurrence at a time: an assembly placing -/// two parts in turn converts in a few steps of many solids (the first finds both parts' -/// orientations side by side on the workers, the next places every other instance), where -/// converting instance by instance took one step each. +/// two parts in turn converts in one step (both parts' orientations found side by side on the +/// workers, every other instance placed after its part's first), where converting instance by +/// instance took one step each, and stopping the step at a part's second instance two. #[test] fn distinct_parts_convert_together_across_instances() { let n = 24; @@ -784,7 +784,7 @@ fn distinct_parts_convert_together_across_instances() { while reading.step() { steps += 1; } - assert_eq!(steps, 2, "both parts in the first step, every other instance in the second"); + assert_eq!(steps, 1, "both parts and every other instance in one step"); let threaded = reading.finish().unwrap(); assert_eq!(threaded.bodies.len(), n as usize); assert!(threaded.warnings.is_empty(), "{:?}", threaded.warnings);