Fix execution caching to cache artifact graph NodePath (#6978)
* Fix to add NodePaths to SketchOnFace and SketchOnPlane artifacts * Fix to only compute the new part of the artifact graph * Change to early-return sooner when in mock mode * Add another test * Fix to propagate NodePath for sketch on face * Update output
This commit is contained in:
@ -115,7 +115,7 @@ where
|
||||
seq.end()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeRef {
|
||||
@ -396,7 +396,6 @@ pub enum Artifact {
|
||||
Cap(Cap),
|
||||
SweepEdge(SweepEdge),
|
||||
EdgeCut(EdgeCut),
|
||||
#[expect(unused)]
|
||||
EdgeCutEdge(EdgeCutEdge),
|
||||
Helix(Helix),
|
||||
}
|
||||
@ -550,8 +549,9 @@ impl Artifact {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub(crate) fn code_ref(&self) -> Option<&CodeRef> {
|
||||
/// The [`CodeRef`] for the artifact itself. See also
|
||||
/// [`Self::face_code_ref`].
|
||||
pub fn code_ref(&self) -> Option<&CodeRef> {
|
||||
match self {
|
||||
Artifact::CompositeSolid(a) => Some(&a.code_ref),
|
||||
Artifact::Plane(a) => Some(&a.code_ref),
|
||||
@ -570,6 +570,24 @@ impl Artifact {
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`CodeRef`] referring to the face artifact that it's on, not the
|
||||
/// artifact itself.
|
||||
pub fn face_code_ref(&self) -> Option<&CodeRef> {
|
||||
match self {
|
||||
Artifact::CompositeSolid(_)
|
||||
| Artifact::Plane(_)
|
||||
| Artifact::Path(_)
|
||||
| Artifact::Segment(_)
|
||||
| Artifact::Solid2d(_)
|
||||
| Artifact::StartSketchOnFace(_)
|
||||
| Artifact::StartSketchOnPlane(_)
|
||||
| Artifact::Sweep(_) => None,
|
||||
Artifact::Wall(a) => Some(&a.face_code_ref),
|
||||
Artifact::Cap(a) => Some(&a.face_code_ref),
|
||||
Artifact::SweepEdge(_) | Artifact::EdgeCut(_) | Artifact::EdgeCutEdge(_) | Artifact::Helix(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge the new artifact into self. If it can't because it's a different
|
||||
/// type, return the new artifact which should be used as a replacement.
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
@ -704,6 +722,19 @@ impl ArtifactGraph {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.map.is_empty()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &Artifact> {
|
||||
self.map.values()
|
||||
}
|
||||
|
||||
/// Consume the artifact graph and return the map of artifacts.
|
||||
fn into_map(self) -> IndexMap<ArtifactId, Artifact> {
|
||||
self.map
|
||||
}
|
||||
|
||||
/// Used to make the mermaid tests deterministic.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn sort(&mut self) {
|
||||
@ -712,17 +743,29 @@ impl ArtifactGraph {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the artifact graph from the artifact commands and the responses. The
|
||||
/// initial graph is the graph cached from a previous execution. NodePaths of
|
||||
/// `exec_artifacts` are filled in from the AST.
|
||||
pub(super) fn build_artifact_graph(
|
||||
artifact_commands: &[ArtifactCommand],
|
||||
responses: &IndexMap<Uuid, WebSocketResponse>,
|
||||
ast: &Node<Program>,
|
||||
exec_artifacts: &IndexMap<ArtifactId, Artifact>,
|
||||
exec_artifacts: &mut IndexMap<ArtifactId, Artifact>,
|
||||
initial_graph: ArtifactGraph,
|
||||
) -> Result<ArtifactGraph, KclError> {
|
||||
let mut map = IndexMap::new();
|
||||
let mut map = initial_graph.into_map();
|
||||
|
||||
let mut path_to_plane_id_map = FnvHashMap::default();
|
||||
let mut current_plane_id = None;
|
||||
|
||||
// Fill in NodePaths for artifacts that were added directly to the map
|
||||
// during execution.
|
||||
for exec_artifact in exec_artifacts.values_mut() {
|
||||
// Note: We only have access to the new AST. So if these artifacts
|
||||
// somehow came from cached AST, this won't fill in anything.
|
||||
fill_in_node_paths(exec_artifact, ast);
|
||||
}
|
||||
|
||||
for artifact_command in artifact_commands {
|
||||
if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
|
||||
current_plane_id = Some(entity_id);
|
||||
@ -762,6 +805,24 @@ pub(super) fn build_artifact_graph(
|
||||
Ok(ArtifactGraph { map })
|
||||
}
|
||||
|
||||
/// These may have been created with placeholder `CodeRef`s because we didn't
|
||||
/// have the entire AST available. Now we fill them in.
|
||||
fn fill_in_node_paths(artifact: &mut Artifact, program: &Node<Program>) {
|
||||
match artifact {
|
||||
Artifact::StartSketchOnFace(face) => {
|
||||
if face.code_ref.node_path.is_empty() {
|
||||
face.code_ref.node_path = NodePath::from_range(program, face.code_ref.range).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
Artifact::StartSketchOnPlane(plane) => {
|
||||
if plane.code_ref.node_path.is_empty() {
|
||||
plane.code_ref.node_path = NodePath::from_range(program, plane.code_ref.range).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Flatten the responses into a map of command IDs to modeling command
|
||||
/// responses. The raw responses from the engine contain batches.
|
||||
fn flatten_modeling_command_responses(
|
||||
@ -846,6 +907,12 @@ fn artifacts_to_update(
|
||||
ast: &Node<Program>,
|
||||
exec_artifacts: &IndexMap<ArtifactId, Artifact>,
|
||||
) -> Result<Vec<Artifact>, KclError> {
|
||||
let uuid = artifact_command.cmd_id;
|
||||
let Some(response) = responses.get(&uuid) else {
|
||||
// Response not found or not successful.
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
// TODO: Build path-to-node from artifact_command source range. Right now,
|
||||
// we're serializing an empty array, and the TS wrapper fills it in with the
|
||||
// correct value based on NodePath.
|
||||
@ -858,14 +925,7 @@ fn artifacts_to_update(
|
||||
path_to_node,
|
||||
};
|
||||
|
||||
let uuid = artifact_command.cmd_id;
|
||||
let id = ArtifactId::new(uuid);
|
||||
|
||||
let Some(response) = responses.get(&uuid) else {
|
||||
// Response not found or not successful.
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
let cmd = &artifact_command.command;
|
||||
|
||||
match cmd {
|
||||
@ -1100,16 +1160,19 @@ fn artifacts_to_update(
|
||||
let extra_artifact = exec_artifacts.values().find(|a| {
|
||||
if let Artifact::StartSketchOnFace(s) = a {
|
||||
s.face_id == face_id
|
||||
} else if let Artifact::StartSketchOnPlane(s) = a {
|
||||
s.plane_id == face_id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
let sketch_on_face_source_range = extra_artifact
|
||||
let sketch_on_face_code_ref = extra_artifact
|
||||
.and_then(|a| match a {
|
||||
Artifact::StartSketchOnFace(s) => Some(s.code_ref.range),
|
||||
// TODO: If we didn't find it, it's probably a bug.
|
||||
Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
|
||||
Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
|
||||
_ => None,
|
||||
})
|
||||
// TODO: If we didn't find it, it's probably a bug.
|
||||
.unwrap_or_default();
|
||||
|
||||
return_arr.push(Artifact::Wall(Wall {
|
||||
@ -1118,11 +1181,7 @@ fn artifacts_to_update(
|
||||
edge_cut_edge_ids: Vec::new(),
|
||||
sweep_id: path_sweep_id,
|
||||
path_ids: Vec::new(),
|
||||
face_code_ref: CodeRef {
|
||||
range: sketch_on_face_source_range,
|
||||
node_path: NodePath::from_range(ast, sketch_on_face_source_range).unwrap_or_default(),
|
||||
path_to_node: Vec::new(),
|
||||
},
|
||||
face_code_ref: sketch_on_face_code_ref,
|
||||
cmd_id: artifact_command.cmd_id,
|
||||
}));
|
||||
let mut new_seg = seg.clone();
|
||||
@ -1155,15 +1214,19 @@ fn artifacts_to_update(
|
||||
let extra_artifact = exec_artifacts.values().find(|a| {
|
||||
if let Artifact::StartSketchOnFace(s) = a {
|
||||
s.face_id == face_id
|
||||
} else if let Artifact::StartSketchOnPlane(s) = a {
|
||||
s.plane_id == face_id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
let sketch_on_face_source_range = extra_artifact
|
||||
let sketch_on_face_code_ref = extra_artifact
|
||||
.and_then(|a| match a {
|
||||
Artifact::StartSketchOnFace(s) => Some(s.code_ref.range),
|
||||
Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
|
||||
Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
|
||||
_ => None,
|
||||
})
|
||||
// TODO: If we didn't find it, it's probably a bug.
|
||||
.unwrap_or_default();
|
||||
return_arr.push(Artifact::Cap(Cap {
|
||||
id: face_id,
|
||||
@ -1171,11 +1234,7 @@ fn artifacts_to_update(
|
||||
edge_cut_edge_ids: Vec::new(),
|
||||
sweep_id: path_sweep_id,
|
||||
path_ids: Vec::new(),
|
||||
face_code_ref: CodeRef {
|
||||
range: sketch_on_face_source_range,
|
||||
node_path: NodePath::from_range(ast, sketch_on_face_source_range).unwrap_or_default(),
|
||||
path_to_node: Vec::new(),
|
||||
},
|
||||
face_code_ref: sketch_on_face_code_ref,
|
||||
cmd_id: artifact_command.cmd_id,
|
||||
}));
|
||||
let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
|
||||
|
@ -1155,23 +1155,24 @@ impl ExecutorContext {
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
{
|
||||
// Move the artifact commands and responses to simplify cache management
|
||||
// and error creation.
|
||||
exec_state
|
||||
.global
|
||||
.artifact_commands
|
||||
.extend(self.engine.take_artifact_commands().await);
|
||||
exec_state
|
||||
.global
|
||||
.artifact_responses
|
||||
.extend(self.engine.take_responses().await);
|
||||
let new_commands = self.engine.take_artifact_commands().await;
|
||||
let new_responses = self.engine.take_responses().await;
|
||||
let initial_graph = exec_state.global.artifact_graph.clone();
|
||||
|
||||
// Build the artifact graph.
|
||||
match build_artifact_graph(
|
||||
&exec_state.global.artifact_commands,
|
||||
&exec_state.global.artifact_responses,
|
||||
let graph_result = build_artifact_graph(
|
||||
&new_commands,
|
||||
&new_responses,
|
||||
program,
|
||||
&exec_state.global.artifacts,
|
||||
) {
|
||||
&mut exec_state.global.artifacts,
|
||||
initial_graph,
|
||||
);
|
||||
// Move the artifact commands and responses into ExecState to
|
||||
// simplify cache management and error creation.
|
||||
exec_state.global.artifact_commands.extend(new_commands);
|
||||
exec_state.global.artifact_responses.extend(new_responses);
|
||||
|
||||
match graph_result {
|
||||
Ok(artifact_graph) => {
|
||||
exec_state.global.artifact_graph = artifact_graph;
|
||||
exec_result.map(|(_, env_ref, _)| env_ref)
|
||||
|
Reference in New Issue
Block a user