Deterministic parallelized snaps (#6527)

* initial pass

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

changes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

more updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

more updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

serde variant name

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

some sort

Signed-off-by: Jess Frazelle <github@jessfraz.com>

some sort

Signed-off-by: Jess Frazelle <github@jessfraz.com>

some sort

Signed-off-by: Jess Frazelle <github@jessfraz.com>

some sort

Signed-off-by: Jess Frazelle <github@jessfraz.com>

some sort

Signed-off-by: Jess Frazelle <github@jessfraz.com>

some sort

Signed-off-by: Jess Frazelle <github@jessfraz.com>

some sort

Signed-off-by: Jess Frazelle <github@jessfraz.com>

some sort

Signed-off-by: Jess Frazelle <github@jessfraz.com>

some sort

Signed-off-by: Jess Frazelle <github@jessfraz.com>

some sort

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

sort the edges

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

u[dates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

u[dates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

cleanups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

add bs-to-kcl

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-04-29 06:38:52 -07:00
committed by GitHub
parent a173a82d59
commit 77e3efde9a
302 changed files with 233255 additions and 244172 deletions

View File

@ -38,7 +38,27 @@ pub struct ArtifactCommand {
pub command: ModelingCmd,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, ts_rs::TS, JsonSchema)]
impl PartialOrd for ArtifactCommand {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
// Order by the source range.
let range = self.range.cmp(&other.range);
if range != std::cmp::Ordering::Equal {
return Some(range);
}
#[cfg(test)]
{
// If the ranges are equal, order by the serde variant.
Some(
crate::variant_name::variant_name(&self.command)
.cmp(&crate::variant_name::variant_name(&other.command)),
)
}
#[cfg(not(test))]
self.cmd_id.partial_cmp(&other.cmd_id)
}
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Ord, PartialOrd, Hash, ts_rs::TS, JsonSchema)]
#[ts(export_to = "Artifact.ts")]
pub struct ArtifactId(Uuid);
@ -194,7 +214,7 @@ pub struct Sweep {
pub code_ref: CodeRef,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
pub enum SweepSubType {
@ -245,6 +265,8 @@ pub struct Wall {
/// This is for the sketch-on-face plane, not for the wall itself. Traverse
/// to the extrude and/or segment to get the wall's code_ref.
pub face_code_ref: CodeRef,
/// The command ID that got the data for this wall.
pub cmd_id: uuid::Uuid,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
@ -263,7 +285,7 @@ pub struct Cap {
pub face_code_ref: CodeRef,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Ord, PartialOrd, Eq, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
pub enum CapSubType {
@ -278,12 +300,13 @@ pub struct SweepEdge {
pub id: ArtifactId,
pub sub_type: SweepEdgeSubType,
pub seg_id: ArtifactId,
pub cmd_id: uuid::Uuid,
pub sweep_id: ArtifactId,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub common_surface_ids: Vec<ArtifactId>,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Ord, PartialOrd, Eq, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
pub enum SweepEdgeSubType {
@ -305,7 +328,7 @@ pub struct EdgeCut {
pub code_ref: CodeRef,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Eq, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
pub enum EdgeCutSubType {
@ -362,6 +385,122 @@ pub enum Artifact {
Helix(Helix),
}
impl Artifact {
pub(crate) fn rank(&self) -> u8 {
match self {
Artifact::Plane(_) => 0,
Artifact::StartSketchOnPlane(_) => 1,
Artifact::StartSketchOnFace(_) => 2,
Artifact::Path(_) => 3,
Artifact::Segment(_) => 4,
Artifact::Solid2d(_) => 5,
Artifact::Sweep(_) => 6,
Artifact::CompositeSolid(_) => 7,
Artifact::Wall(_) => 8,
Artifact::Cap(Cap { sub_type, .. }) if *sub_type == CapSubType::Start => 9,
Artifact::Cap(Cap { sub_type, .. }) if *sub_type == CapSubType::Start => 10,
Artifact::Cap(_) => 11,
Artifact::SweepEdge(SweepEdge { sub_type, .. }) if *sub_type == SweepEdgeSubType::Adjacent => 12,
Artifact::SweepEdge(SweepEdge { sub_type, .. }) if *sub_type == SweepEdgeSubType::Opposite => 13,
Artifact::SweepEdge(_) => 14,
Artifact::EdgeCut(_) => 15,
Artifact::EdgeCutEdge(_) => 16,
Artifact::Helix(_) => 17,
}
}
}
impl PartialOrd for Artifact {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
// The only thing we want to sort is if we have two sweep edges, we want
// to sort them by the sub_type.
match (self, other) {
(Artifact::SweepEdge(a), Artifact::SweepEdge(b)) => {
if a.sub_type != b.sub_type {
return Some(a.sub_type.cmp(&b.sub_type));
}
if a.sweep_id != b.sweep_id {
return Some(a.sweep_id.cmp(&b.sweep_id));
}
if a.cmd_id != b.cmd_id {
return Some(a.cmd_id.cmp(&b.cmd_id));
}
Some(a.id.cmp(&b.id))
}
(Artifact::Sweep(a), Artifact::Sweep(b)) => {
if a.code_ref.range != b.code_ref.range {
return Some(a.code_ref.range.cmp(&b.code_ref.range));
}
Some(a.id.cmp(&b.id))
}
// Sort the planes by their code_ref range.
(Artifact::Plane(a), Artifact::Plane(b)) => {
if a.code_ref.range != b.code_ref.range {
return Some(a.code_ref.range.cmp(&b.code_ref.range));
}
Some(a.id.cmp(&b.id))
}
// Sort the paths by their code_ref range.
(Artifact::Path(a), Artifact::Path(b)) => {
if a.code_ref.range != b.code_ref.range {
return Some(a.code_ref.range.cmp(&b.code_ref.range));
}
Some(a.id.cmp(&b.id))
}
// Sort the segments by their code_ref range.
(Artifact::Segment(a), Artifact::Segment(b)) => {
if a.code_ref.range != b.code_ref.range {
return Some(a.code_ref.range.cmp(&b.code_ref.range));
}
Some(a.id.cmp(&b.id))
}
// Sort the solid2d by their id.
(Artifact::Solid2d(a), Artifact::Solid2d(b)) => {
if a.path_id != b.path_id {
return Some(a.path_id.cmp(&b.path_id));
}
Some(a.id.cmp(&b.id))
}
// Sort the walls by their code_ref range.
(Artifact::Wall(a), Artifact::Wall(b)) => {
if a.sweep_id != b.sweep_id {
return Some(a.sweep_id.cmp(&b.sweep_id));
}
if a.cmd_id != b.cmd_id {
return Some(a.cmd_id.cmp(&b.cmd_id));
}
if a.face_code_ref.range != b.face_code_ref.range {
return Some(a.face_code_ref.range.cmp(&b.face_code_ref.range));
}
if a.seg_id != b.seg_id {
return Some(a.seg_id.cmp(&b.seg_id));
}
Some(a.id.cmp(&b.id))
}
// Sort the caps by their code_ref range.
(Artifact::Cap(a), Artifact::Cap(b)) => {
if a.sub_type != b.sub_type {
return Some(a.sub_type.cmp(&b.sub_type));
}
if a.sweep_id != b.sweep_id {
return Some(a.sweep_id.cmp(&b.sweep_id));
}
if a.face_code_ref.range != b.face_code_ref.range {
return Some(a.face_code_ref.range.cmp(&b.face_code_ref.range));
}
Some(a.id.cmp(&b.id))
}
(Artifact::CompositeSolid(a), Artifact::CompositeSolid(b)) => Some(a.id.cmp(&b.id)),
(Artifact::StartSketchOnFace(a), Artifact::StartSketchOnFace(b)) => Some(a.id.cmp(&b.id)),
(Artifact::StartSketchOnPlane(a), Artifact::StartSketchOnPlane(b)) => Some(a.id.cmp(&b.id)),
// Planes are first, then paths, then segments, then solids2ds, then sweeps, then
// walls, then caps, then sweep edges, then edge cuts, then edge cut edges, then
// helixes.
_ => Some(self.rank().cmp(&other.rank())),
}
}
}
impl Artifact {
pub(crate) fn id(&self) -> ArtifactId {
match self {
@ -533,6 +672,13 @@ impl ArtifactGraph {
pub fn len(&self) -> usize {
self.map.len()
}
/// Used to make the mermaid tests deterministic.
#[cfg(test)]
pub(crate) fn sort(&mut self) {
self.map
.sort_by(|_ak, av, _bk, bv| av.partial_cmp(bv).unwrap_or(std::cmp::Ordering::Equal));
}
}
pub(super) fn build_artifact_graph(
@ -709,6 +855,7 @@ fn artifacts_to_update(
sweep_id: wall.sweep_id,
path_ids: wall.path_ids.clone(),
face_code_ref: wall.face_code_ref.clone(),
cmd_id: artifact_command.cmd_id,
})]);
}
Some(Artifact::Cap(cap)) => {
@ -769,6 +916,7 @@ fn artifacts_to_update(
sweep_id: wall.sweep_id,
path_ids: vec![id],
face_code_ref: wall.face_code_ref.clone(),
cmd_id: artifact_command.cmd_id,
}));
}
if let Some(Artifact::Cap(cap)) = plane {
@ -933,6 +1081,7 @@ fn artifacts_to_update(
range: sketch_on_face_source_range,
path_to_node: Vec::new(),
},
cmd_id: artifact_command.cmd_id,
}));
let mut new_seg = seg.clone();
new_seg.surface_id = Some(face_id);
@ -1044,6 +1193,7 @@ fn artifacts_to_update(
id: response_edge_id,
sub_type,
seg_id: edge_id,
cmd_id: artifact_command.cmd_id,
sweep_id: sweep.id,
common_surface_ids: Vec::new(),
}));

View File

@ -194,6 +194,7 @@ impl ArtifactGraph {
let mut next_id = 1_u32;
let mut stable_id_map = FnvHashMap::default();
for id in self.map.keys() {
stable_id_map.insert(*id, next_id);
next_id = next_id.checked_add(1).unwrap();
@ -452,6 +453,7 @@ impl ArtifactGraph {
}
// Output the edges.
edges.par_sort_by(|ak, _, bk, _| (if ak.0 == bk.0 { ak.1.cmp(&bk.1) } else { ak.0.cmp(&bk.0) }));
for ((source_id, target_id), edge) in edges {
let extra = match edge.kind {
// Extra length. This is needed to make the graph layout more

View File

@ -49,6 +49,33 @@ pub enum Operation {
GroupEnd,
}
/// A way for sorting the operations in the timeline. This is used to sort
/// operations in the timeline and to determine the order of operations.
/// We use this for the multi-threaded snapshotting, so that we can have deterministic
/// output.
impl PartialOrd for Operation {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(match (self, other) {
(Self::StdLibCall { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
(Self::StdLibCall { source_range: a, .. }, Self::KclStdLibCall { source_range: b, .. }) => a.cmp(b),
(Self::StdLibCall { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
(Self::StdLibCall { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
(Self::KclStdLibCall { source_range: a, .. }, Self::KclStdLibCall { source_range: b, .. }) => a.cmp(b),
(Self::KclStdLibCall { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
(Self::KclStdLibCall { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
(Self::KclStdLibCall { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
(Self::GroupBegin { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
(Self::GroupBegin { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
(Self::GroupBegin { source_range: a, .. }, Self::KclStdLibCall { source_range: b, .. }) => a.cmp(b),
(Self::GroupBegin { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
(Self::GroupEnd, Self::StdLibCall { .. }) => std::cmp::Ordering::Greater,
(Self::GroupEnd, Self::KclStdLibCall { .. }) => std::cmp::Ordering::Greater,
(Self::GroupEnd, Self::GroupBegin { .. }) => std::cmp::Ordering::Greater,
(Self::GroupEnd, Self::GroupEnd) => std::cmp::Ordering::Equal,
})
}
}
impl Operation {
/// If the variant is `StdLibCall`, set the `is_error` field.
pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {

View File

@ -717,37 +717,9 @@ impl ExecutorContext {
self.run_concurrent(program, exec_state, false).await
}
/// Perform the execution of a program.
///
/// You can optionally pass in some initialization memory for partial
/// execution.
///
/// To access non-fatal errors and warnings, extract them from the `ExecState`.
pub async fn run_single_threaded(
&self,
program: &crate::Program,
exec_state: &mut ExecState,
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
exec_state.add_root_module_contents(program);
#[cfg(test)]
{
exec_state.single_threaded = true;
}
self.eval_prelude(exec_state, SourceRange::synthetic())
.await
.map_err(KclErrorWithOutputs::no_outputs)?;
self.inner_run(program, exec_state, false).await
}
/// Perform the execution of a program using an (experimental!) concurrent
/// Perform the execution of a program using a concurrent
/// execution model. This has the same signature as [Self::run].
///
/// For now -- do not use this unless you're willing to accept some
/// breakage.
///
/// You can optionally pass in some initialization memory for partial
/// execution.
///

View File

@ -32,9 +32,6 @@ pub struct ExecState {
pub(super) global: GlobalState,
pub(super) mod_local: ModuleState,
pub(super) exec_context: Option<super::ExecutorContext>,
/// If we should not parallelize execution.
#[cfg(test)]
pub single_threaded: bool,
}
pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
@ -96,8 +93,6 @@ impl ExecState {
global: GlobalState::new(&exec_context.settings),
mod_local: ModuleState::new(None, ProgramMemory::new(), Default::default()),
exec_context: Some(exec_context.clone()),
#[cfg(test)]
single_threaded: false,
}
}
@ -108,8 +103,6 @@ impl ExecState {
global,
mod_local: ModuleState::new(None, ProgramMemory::new(), Default::default()),
exec_context: Some(exec_context.clone()),
#[cfg(test)]
single_threaded: false,
};
}