ignore errors on the modeling cmds that are only for the artifact graph

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-04-04 16:07:48 -07:00
parent f6ffb4a01e
commit db62c1962d
10 changed files with 103 additions and 44 deletions

View File

@ -51,6 +51,8 @@ pub struct EngineConnection {
/// If the server sends session data, it'll be copied to here. /// If the server sends session data, it'll be copied to here.
session_data: Arc<RwLock<Option<ModelingSessionData>>>, session_data: Arc<RwLock<Option<ModelingSessionData>>>,
ignore_failed_responses: Arc<RwLock<Vec<uuid::Uuid>>>,
execution_kind: Arc<RwLock<ExecutionKind>>, execution_kind: Arc<RwLock<ExecutionKind>>,
stats: EngineStats, stats: EngineStats,
} }
@ -343,6 +345,7 @@ impl EngineConnection {
batch_end: Arc::new(RwLock::new(IndexMap::new())), batch_end: Arc::new(RwLock::new(IndexMap::new())),
artifact_commands: Arc::new(RwLock::new(Vec::new())), artifact_commands: Arc::new(RwLock::new(Vec::new())),
default_planes: Default::default(), default_planes: Default::default(),
ignore_failed_responses: Arc::new(RwLock::new(Vec::new())),
session_data, session_data,
execution_kind: Default::default(), execution_kind: Default::default(),
stats: Default::default(), stats: Default::default(),
@ -364,6 +367,10 @@ impl EngineManager for EngineConnection {
self.responses.clone() self.responses.clone()
} }
fn ignore_failed_responses(&self) -> Arc<RwLock<Vec<Uuid>>> {
self.ignore_failed_responses.clone()
}
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> { fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
self.artifact_commands.clone() self.artifact_commands.clone()
} }

View File

@ -32,6 +32,7 @@ pub struct EngineConnection {
execution_kind: Arc<RwLock<ExecutionKind>>, execution_kind: Arc<RwLock<ExecutionKind>>,
/// The default planes for the scene. /// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>, default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
ignore_failed_responses: Arc<RwLock<Vec<uuid::Uuid>>>,
stats: EngineStats, stats: EngineStats,
} }
@ -43,6 +44,7 @@ impl EngineConnection {
artifact_commands: Arc::new(RwLock::new(Vec::new())), artifact_commands: Arc::new(RwLock::new(Vec::new())),
execution_kind: Default::default(), execution_kind: Default::default(),
default_planes: Default::default(), default_planes: Default::default(),
ignore_failed_responses: Arc::new(RwLock::new(Vec::new())),
stats: Default::default(), stats: Default::default(),
}) })
} }
@ -62,6 +64,10 @@ impl crate::engine::EngineManager for EngineConnection {
Arc::new(RwLock::new(IndexMap::new())) Arc::new(RwLock::new(IndexMap::new()))
} }
fn ignore_failed_responses(&self) -> Arc<RwLock<Vec<uuid::Uuid>>> {
self.ignore_failed_responses.clone()
}
fn stats(&self) -> &EngineStats { fn stats(&self) -> &EngineStats {
&self.stats &self.stats
} }

View File

@ -43,6 +43,7 @@ pub struct EngineConnection {
responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>, responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>,
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>, artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
execution_kind: Arc<RwLock<ExecutionKind>>, execution_kind: Arc<RwLock<ExecutionKind>>,
ignore_failed_responses: Arc<RwLock<Vec<uuid::Uuid>>>,
/// The default planes for the scene. /// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>, default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
stats: EngineStats, stats: EngineStats,
@ -63,6 +64,7 @@ impl EngineConnection {
artifact_commands: Arc::new(RwLock::new(Vec::new())), artifact_commands: Arc::new(RwLock::new(Vec::new())),
execution_kind: Default::default(), execution_kind: Default::default(),
default_planes: Default::default(), default_planes: Default::default(),
ignore_failed_responses: Arc::new(RwLock::new(Vec::new())),
stats: Default::default(), stats: Default::default(),
}) })
} }
@ -156,6 +158,10 @@ impl crate::engine::EngineManager for EngineConnection {
self.responses.clone() self.responses.clone()
} }
fn ignore_failed_responses(&self) -> Arc<RwLock<Vec<uuid::Uuid>>> {
self.ignore_failed_responses.clone()
}
fn stats(&self) -> &EngineStats { fn stats(&self) -> &EngineStats {
&self.stats &self.stats
} }

View File

@ -90,6 +90,9 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
/// Get the command responses from the engine. /// Get the command responses from the engine.
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>; fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>;
/// Ignore failed responses of these command ids.
fn ignore_failed_responses(&self) -> Arc<RwLock<Vec<Uuid>>>;
/// Get the artifact commands that have accumulated so far. /// Get the artifact commands that have accumulated so far.
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>>; fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>>;
@ -150,6 +153,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn clear_queues(&self) { async fn clear_queues(&self) {
self.batch().write().await.clear(); self.batch().write().await.clear();
self.batch_end().write().await.clear(); self.batch_end().write().await.clear();
self.ignore_failed_responses().write().await.clear();
} }
/// Send a modeling command and wait for the response message. /// Send a modeling command and wait for the response message.
@ -296,6 +300,21 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
Ok(()) Ok(())
} }
// Add a modeling command to the batch but don't fire it right away.
// Ignore the failure of this command.
async fn batch_modeling_cmd_ignore_failure(
&self,
id: uuid::Uuid,
source_range: SourceRange,
cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> {
self.ignore_failed_responses().write().await.push(id);
self.batch_modeling_cmd(id, source_range, cmd).await?;
Ok(())
}
// Add a vector of modeling commands to the batch but don't fire it right away. // Add a vector of modeling commands to the batch but don't fire it right away.
// This allows you to force them all to be added together in the same order. // This allows you to force them all to be added together in the same order.
// When we are running things in parallel this prevents race conditions that might come // When we are running things in parallel this prevents race conditions that might come
@ -467,6 +486,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
if let OkWebSocketResponseData::ModelingBatch { responses } = response { if let OkWebSocketResponseData::ModelingBatch { responses } = response {
let responses = responses.into_iter().map(|(k, v)| (Uuid::from(k), v)).collect(); let responses = responses.into_iter().map(|(k, v)| (Uuid::from(k), v)).collect();
self.parse_batch_responses(last_id.into(), id_to_source_range, responses) self.parse_batch_responses(last_id.into(), id_to_source_range, responses)
.await
} else { } else {
// We should never get here. // We should never get here.
Err(KclError::Engine(KclErrorDetails { Err(KclError::Engine(KclErrorDetails {
@ -657,7 +677,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
} }
} }
fn parse_batch_responses( async fn parse_batch_responses(
&self, &self,
// The last response we are looking for. // The last response we are looking for.
id: uuid::Uuid, id: uuid::Uuid,
@ -685,6 +705,16 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
} }
} }
BatchResponse::Failure { errors } => { BatchResponse::Failure { errors } => {
// If this was in our ignore list, we don't care about it.
if self
.ignore_failed_responses()
.read()
.await
.iter()
.any(|id| id == cmd_id)
{
continue;
}
// Get the source range for the command. // Get the source range for the command.
let source_range = id_to_source_range.get(cmd_id).cloned().ok_or_else(|| { let source_range = id_to_source_range.get(cmd_id).cloned().ok_or_else(|| {
KclError::Engine(KclErrorDetails { KclError::Engine(KclErrorDetails {

View File

@ -370,6 +370,17 @@ impl Args {
self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await
} }
pub(crate) async fn batch_modeling_cmd_ignore_failure(
&self,
id: uuid::Uuid,
cmd: ModelingCmd,
) -> Result<(), crate::errors::KclError> {
self.ctx
.engine
.batch_modeling_cmd_ignore_failure(id, self.source_range, &cmd)
.await
}
// Add multiple modeling commands to the batch but don't fire them right away. // Add multiple modeling commands to the batch but don't fire them right away.
pub(crate) async fn batch_modeling_cmds(&self, cmds: &[ModelingCmdReq]) -> Result<(), crate::errors::KclError> { pub(crate) async fn batch_modeling_cmds(&self, cmds: &[ModelingCmdReq]) -> Result<(), crate::errors::KclError> {
self.ctx.engine.batch_modeling_cmds(self.source_range, cmds).await self.ctx.engine.batch_modeling_cmds(self.source_range, cmds).await

View File

@ -130,7 +130,6 @@ async fn inner_extrude(
sketch, sketch,
id.into(), id.into(),
length, length,
false,
&NamedCapTags { &NamedCapTags {
start: tag_start.as_ref(), start: tag_start.as_ref(),
end: tag_end.as_ref(), end: tag_end.as_ref(),
@ -155,7 +154,6 @@ pub(crate) async fn do_post_extrude<'a>(
sketch: &Sketch, sketch: &Sketch,
solid_id: ArtifactId, solid_id: ArtifactId,
length: f64, length: f64,
sectional: bool,
named_cap_tags: &'a NamedCapTags<'a>, named_cap_tags: &'a NamedCapTags<'a>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: &Args, args: &Args,
@ -208,45 +206,43 @@ pub(crate) async fn do_post_extrude<'a>(
vec![] vec![]
}; };
// Face filtering attempt in order to resolve https://github.com/KittyCAD/modeling-app/issues/5328 for (curve_id, face_id) in face_infos
// We need to not run Solid3dGetOppositeEdge and Solid3dGetNextAdjacentEdge because it is too .iter()
// hard to know when they work or fail. .filter(|face_info| face_info.cap == ExtrusionFaceCapType::None)
if !sectional { .filter_map(|face_info| {
for (curve_id, face_id) in face_infos if let (Some(curve_id), Some(face_id)) = (face_info.curve_id, face_info.face_id) {
.iter() Some((curve_id, face_id))
.filter(|face_info| face_info.cap == ExtrusionFaceCapType::None) } else {
.filter_map(|face_info| { None
if let (Some(curve_id), Some(face_id)) = (face_info.curve_id, face_info.face_id) { }
Some((curve_id, face_id)) })
} else { {
None // Batch these commands, because the Rust code doesn't actually care about the outcome.
} // So, there's no need to await them.
}) // Instead, the Typescript codebases (which handles WebSocket sends when compiled via Wasm)
{ // uses this to build the artifact graph, which the UI needs.
// Batch these commands, because the Rust code doesn't actually care about the outcome. //
// So, there's no need to await them. // We ignore the failure here because if one of these fails, in the case of a sectional
// Instead, the Typescript codebases (which handles WebSocket sends when compiled via Wasm) // sweep, the whole model should not fail, this is merely for the artifact graph.
// uses this to build the artifact graph, which the UI needs. args.batch_modeling_cmd_ignore_failure(
args.batch_modeling_cmd( exec_state.next_uuid(),
exec_state.next_uuid(), ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
ModelingCmd::from(mcmd::Solid3dGetOppositeEdge { edge_id: curve_id,
edge_id: curve_id, object_id: sketch.id,
object_id: sketch.id, face_id,
face_id, }),
}), )
) .await?;
.await?;
args.batch_modeling_cmd( args.batch_modeling_cmd_ignore_failure(
exec_state.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge { ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
edge_id: curve_id, edge_id: curve_id,
object_id: sketch.id, object_id: sketch.id,
face_id, face_id,
}), }),
) )
.await?; .await?;
}
} }
let Faces { let Faces {

View File

@ -175,7 +175,6 @@ async fn inner_loft(
&sketch, &sketch,
id.into(), id.into(),
0.0, 0.0,
false,
&super::extrude::NamedCapTags { &super::extrude::NamedCapTags {
start: tag_start.as_ref(), start: tag_start.as_ref(),
end: tag_end.as_ref(), end: tag_end.as_ref(),

View File

@ -107,7 +107,6 @@ async fn inner_revolve(
sketch, sketch,
id.into(), id.into(),
0.0, 0.0,
false,
&super::extrude::NamedCapTags { &super::extrude::NamedCapTags {
start: tag_start.as_ref(), start: tag_start.as_ref(),
end: tag_end.as_ref(), end: tag_end.as_ref(),

View File

@ -211,7 +211,6 @@ async fn inner_sweep(
sketch, sketch,
id.into(), id.into(),
0.0, 0.0,
sectional,
&super::extrude::NamedCapTags { &super::extrude::NamedCapTags {
start: tag_start.as_ref(), start: tag_start.as_ref(),
end: tag_end.as_ref(), end: tag_end.as_ref(),

View File

@ -24,6 +24,7 @@ pub struct EngineConnection {
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>>, batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>>,
core_test: Arc<RwLock<String>>, core_test: Arc<RwLock<String>>,
execution_kind: Arc<RwLock<ExecutionKind>>, execution_kind: Arc<RwLock<ExecutionKind>>,
ignore_failed_responses: Arc<RwLock<Vec<uuid::Uuid>>>,
/// The default planes for the scene. /// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>, default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
stats: EngineStats, stats: EngineStats,
@ -39,6 +40,7 @@ impl EngineConnection {
core_test: result, core_test: result,
execution_kind: Default::default(), execution_kind: Default::default(),
default_planes: Default::default(), default_planes: Default::default(),
ignore_failed_responses: Default::default(),
stats: Default::default(), stats: Default::default(),
}) })
} }
@ -371,6 +373,10 @@ impl kcl_lib::EngineManager for EngineConnection {
Arc::new(RwLock::new(IndexMap::new())) Arc::new(RwLock::new(IndexMap::new()))
} }
fn ignore_failed_responses(&self) -> Arc<RwLock<Vec<uuid::Uuid>>> {
self.ignore_failed_responses.clone()
}
fn stats(&self) -> &EngineStats { fn stats(&self) -> &EngineStats {
&self.stats &self.stats
} }