Track artifact commands and operations per-module (#7426)

* Change so that operations are stored per module

* Refactor so that all modeling commands go through ExecState

* Remove unneeded PartialOrd implementations

* Remove artifact_commands from KclError since it was only for debugging

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Jonathan Tran
2025-06-10 21:30:48 -04:00
committed by GitHub
parent 851ea28bd3
commit 9a549ff379
479 changed files with 11575323 additions and 11565198 deletions

View File

@ -45,26 +45,6 @@ pub struct ArtifactCommand {
pub command: ModelingCmd,
}
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)
}
}
pub type DummyPathToNode = Vec<()>;
fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>

View File

@ -111,8 +111,6 @@ impl GlobalState {
#[cfg(feature = "artifact-graph")]
operations: self.exec_state.artifacts.operations,
#[cfg(feature = "artifact-graph")]
artifact_commands: self.exec_state.artifacts.commands,
#[cfg(feature = "artifact-graph")]
artifact_graph: self.exec_state.artifacts.graph,
errors: self.exec_state.errors,
default_planes: ctx.engine.get_default_planes().read().await.clone(),

View File

@ -2,6 +2,8 @@ use indexmap::IndexMap;
use serde::Serialize;
use super::{types::NumericType, ArtifactId, KclValue};
#[cfg(feature = "artifact-graph")]
use crate::parsing::ast::types::{Node, Program};
use crate::{ModuleId, NodePath, SourceRange};
/// A CAD modeling operation for display in the feature tree, AKA operations
@ -37,26 +39,6 @@ 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::GroupBegin { source_range: b, .. }) => a.cmp(b),
(Self::StdLibCall { .. }, 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 { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
(Self::GroupEnd, Self::StdLibCall { .. }) => 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) {
@ -65,6 +47,25 @@ impl Operation {
Self::GroupBegin { .. } | Self::GroupEnd => {}
}
}
#[cfg(feature = "artifact-graph")]
pub(crate) fn fill_node_paths(&mut self, program: &Node<Program>, cached_body_items: usize) {
match self {
Operation::StdLibCall {
node_path,
source_range,
..
}
| Operation::GroupBegin {
node_path,
source_range,
..
} => {
node_path.fill_placeholder(program, cached_body_items, *source_range);
}
Operation::GroupEnd => {}
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]

View File

@ -11,8 +11,8 @@ use crate::{
memory,
state::ModuleState,
types::{NumericType, PrimitiveType, RuntimeType},
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, StatementKind,
TagIdentifier,
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, ModelingCmdMeta, ModuleArtifactState,
PlaneType, StatementKind, TagIdentifier,
},
fmt,
modules::{ModuleId, ModulePath, ModuleRepr},
@ -83,7 +83,7 @@ impl ExecutorContext {
preserve_mem: bool,
module_id: ModuleId,
path: &ModulePath,
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>, ModuleArtifactState), KclError> {
crate::log::log(format!("enter module {path} {}", exec_state.stack()));
let mut local_state = ModuleState::new(path.clone(), exec_state.stack().memory.clone(), Some(module_id));
@ -108,13 +108,16 @@ impl ExecutorContext {
} else {
exec_state.mut_stack().pop_env()
};
if !preserve_mem {
let module_artifacts = if !preserve_mem {
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
}
local_state.artifacts
} else {
Default::default()
};
crate::log::log(format!("leave {path}"));
result.map(|result| (result, env_ref, local_state.module_exports))
result.map(|result| (result, env_ref, local_state.module_exports, module_artifacts))
}
/// Execute an AST's program.
@ -450,12 +453,12 @@ impl ExecutorContext {
if matches!(body_type, BodyType::Root) {
// Flush the batch queue.
self.engine
exec_state
.flush_batch(
ModelingCmdMeta::new(self, SourceRange::new(program.end, program.end, program.module_id)),
// True here tells the engine to flush all the end commands as well like fillets
// and chamfers where the engine would otherwise eat the ID of the segments.
true,
SourceRange::new(program.end, program.end, program.module_id),
)
.await?;
}
@ -535,12 +538,12 @@ impl ExecutorContext {
let result = match &mut repr {
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
ModuleRepr::Kcl(_, Some((_, env_ref, items))) => Ok((*env_ref, items.clone())),
ModuleRepr::Kcl(_, Some((_, env_ref, items, _))) => Ok((*env_ref, items.clone())),
ModuleRepr::Kcl(program, cache) => self
.exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
.await
.map(|(val, er, items)| {
*cache = Some((val, er, items.clone()));
.map(|(val, er, items, module_artifacts)| {
*cache = Some((val, er, items.clone(), module_artifacts.clone()));
(er, items)
}),
ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
@ -566,28 +569,28 @@ impl ExecutorContext {
let result = match &mut repr {
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
ModuleRepr::Kcl(_, Some((val, _, _))) => Ok(val.clone()),
ModuleRepr::Kcl(_, Some((val, _, _, _))) => Ok(val.clone()),
ModuleRepr::Kcl(program, cached_items) => {
let result = self
.exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
.await;
match result {
Ok((val, env, items)) => {
*cached_items = Some((val.clone(), env, items));
Ok((val, env, items, module_artifacts)) => {
*cached_items = Some((val.clone(), env, items, module_artifacts));
Ok(val)
}
Err(e) => Err(e),
}
}
ModuleRepr::Foreign(_, Some(imported)) => Ok(Some(imported.clone())),
ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
ModuleRepr::Foreign(geom, cached) => {
let result = super::import::send_to_engine(geom.clone(), self)
let result = super::import::send_to_engine(geom.clone(), exec_state, self)
.await
.map(|geom| Some(KclValue::ImportedGeometry(geom)));
match result {
Ok(val) => {
*cached = val.clone();
*cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
Ok(val)
}
Err(e) => Err(e),
@ -609,7 +612,7 @@ impl ExecutorContext {
exec_state: &mut ExecState,
source_range: SourceRange,
preserve_mem: bool,
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>, ModuleArtifactState), KclError> {
exec_state.global.mod_loader.enter_module(path);
let result = self
.exec_module_body(program, exec_state, preserve_mem, module_id, path)

View File

@ -15,7 +15,10 @@ use uuid::Uuid;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{annotations, typed_path::TypedPath, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry},
execution::{
annotations, typed_path::TypedPath, types::UnitLen, ExecState, ExecutorContext, ImportedGeometry,
ModelingCmdMeta,
},
fs::FileSystem,
parsing::ast::types::{Annotation, Node},
source_range::SourceRange,
@ -257,15 +260,22 @@ pub struct PreImportedGeometry {
pub source_range: SourceRange,
}
pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) -> Result<ImportedGeometry, KclError> {
pub async fn send_to_engine(
pre: PreImportedGeometry,
exec_state: &mut ExecState,
ctxt: &ExecutorContext,
) -> Result<ImportedGeometry, KclError> {
let imported_geometry = ImportedGeometry::new(
pre.id,
pre.command.files.iter().map(|f| f.path.to_string()).collect(),
vec![pre.source_range.into()],
);
ctxt.engine
.async_modeling_cmd(pre.id, pre.source_range, &ModelingCmd::from(pre.command.clone()))
exec_state
.async_modeling_cmd(
ModelingCmdMeta::with_id(ctxt, pre.source_range, pre.id),
&ModelingCmd::from(pre.command.clone()),
)
.await?;
Ok(imported_geometry)

View File

@ -22,8 +22,10 @@ use kcmc::{
};
use kittycad_modeling_cmds::{self as kcmc, id::ModelingCmdId};
pub use memory::EnvironmentRef;
pub(crate) use modeling::ModelingCmdMeta;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub(crate) use state::ModuleArtifactState;
pub use state::{ExecState, MetaSettings};
use uuid::Uuid;
@ -56,6 +58,7 @@ mod import;
mod import_graph;
pub(crate) mod kcl_value;
mod memory;
mod modeling;
mod state;
pub mod typed_path;
pub(crate) mod types;
@ -76,9 +79,6 @@ pub struct ExecOutcome {
/// the Feature Tree.
#[cfg(feature = "artifact-graph")]
pub operations: Vec<Operation>,
/// Output commands to allow building the artifact graph by the caller.
#[cfg(feature = "artifact-graph")]
pub artifact_commands: Vec<ArtifactCommand>,
/// Output artifact graph.
#[cfg(feature = "artifact-graph")]
pub artifact_graph: ArtifactGraph,
@ -575,7 +575,7 @@ impl ExecutorContext {
let mut mem = exec_state.stack().clone();
let module_infos = exec_state.global.module_infos.clone();
let outcome = exec_state.to_mock_exec_outcome(result.0, self).await;
let outcome = exec_state.into_mock_exec_outcome(result.0, self).await;
mem.squash_env(result.0);
cache::write_old_memory((mem, module_infos)).await;
@ -773,15 +773,12 @@ impl ExecutorContext {
))
.await;
let outcome = exec_state.to_exec_outcome(result.0, self).await;
let outcome = exec_state.into_exec_outcome(result.0, self).await;
Ok(outcome)
}
/// 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(
&self,
@ -794,9 +791,6 @@ impl ExecutorContext {
/// Perform the execution of a program using a concurrent
/// execution model.
///
/// 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_concurrent(
&self,
@ -842,6 +836,8 @@ impl ExecutorContext {
let module_id = *module_id;
let module_path = module_path.clone();
let source_range = SourceRange::from(import_stmt);
// Clone before mutating.
let module_exec_state = exec_state.clone();
self.add_import_module_ops(
exec_state,
@ -853,7 +849,6 @@ impl ExecutorContext {
);
let repr = repr.clone();
let exec_state = exec_state.clone();
let exec_ctxt = self.clone();
let results_tx = results_tx.clone();
@ -873,11 +868,13 @@ impl ExecutorContext {
result.map(|val| ModuleRepr::Kcl(program.clone(), Some(val)))
}
ModuleRepr::Foreign(geom, _) => {
let result = crate::execution::import::send_to_engine(geom.clone(), exec_ctxt)
let result = crate::execution::import::send_to_engine(geom.clone(), exec_state, exec_ctxt)
.await
.map(|geom| Some(KclValue::ImportedGeometry(geom)));
result.map(|val| ModuleRepr::Foreign(geom.clone(), val))
result.map(|val| {
ModuleRepr::Foreign(geom.clone(), Some((val, exec_state.mod_local.artifacts.clone())))
})
}
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::new_internal(KclErrorDetails::new(
format!("Module {module_path} not found in universe"),
@ -889,7 +886,7 @@ impl ExecutorContext {
#[cfg(target_arch = "wasm32")]
{
wasm_bindgen_futures::spawn_local(async move {
let mut exec_state = exec_state;
let mut exec_state = module_exec_state;
let exec_ctxt = exec_ctxt;
let result = exec_module(
@ -911,7 +908,7 @@ impl ExecutorContext {
#[cfg(not(target_arch = "wasm32"))]
{
set.spawn(async move {
let mut exec_state = exec_state;
let mut exec_state = module_exec_state;
let exec_ctxt = exec_ctxt;
let result = exec_module(
@ -964,6 +961,15 @@ impl ExecutorContext {
}
}
// Since we haven't technically started executing the root module yet,
// the operations corresponding to the imports will be missing unless we
// track them here.
#[cfg(all(test, feature = "artifact-graph"))]
exec_state
.global
.root_module_artifacts
.extend(exec_state.mod_local.artifacts.clone());
self.inner_run(program, exec_state, preserve_mem).await
}
@ -993,6 +999,18 @@ impl ExecutorContext {
Ok((universe, root_imports))
}
#[cfg(not(feature = "artifact-graph"))]
fn add_import_module_ops(
&self,
_exec_state: &mut ExecState,
_program: &crate::Program,
_module_id: ModuleId,
_module_path: &ModulePath,
_source_range: SourceRange,
_universe_map: &UniverseMap,
) {
}
#[cfg(feature = "artifact-graph")]
fn add_import_module_ops(
&self,
@ -1042,18 +1060,6 @@ impl ExecutorContext {
}
}
#[cfg(not(feature = "artifact-graph"))]
fn add_import_module_ops(
&self,
_exec_state: &mut ExecState,
_program: &crate::Program,
_module_id: ModuleId,
_module_path: &ModulePath,
_source_range: SourceRange,
_universe_map: &UniverseMap,
) {
}
/// Perform the execution of a program. Accept all possible parameters and
/// output everything.
async fn inner_run(
@ -1121,26 +1127,32 @@ impl ExecutorContext {
&ModulePath::Main,
)
.await;
#[cfg(all(test, feature = "artifact-graph"))]
let exec_result = exec_result.map(|(_, env_ref, _, module_artifacts)| {
exec_state.global.root_module_artifacts.extend(module_artifacts);
env_ref
});
#[cfg(not(all(test, feature = "artifact-graph")))]
let exec_result = exec_result.map(|(_, env_ref, _, _)| env_ref);
#[cfg(feature = "artifact-graph")]
{
// Fill in NodePath for operations.
let cached_body_items = exec_state.global.artifacts.cached_body_items();
for op in exec_state.global.artifacts.operations.iter_mut().skip(start_op) {
match op {
Operation::StdLibCall {
node_path,
source_range,
..
op.fill_node_paths(program, cached_body_items);
}
#[cfg(test)]
{
for op in exec_state.global.root_module_artifacts.operations.iter_mut() {
op.fill_node_paths(program, cached_body_items);
}
for module in exec_state.global.module_infos.values_mut() {
if let ModuleRepr::Kcl(_, Some((_, _, _, module_artifacts))) = &mut module.repr {
for op in &mut module_artifacts.operations {
op.fill_node_paths(program, cached_body_items);
}
}
| Operation::GroupBegin {
node_path,
source_range,
..
} => {
node_path.fill_placeholder(program, cached_body_items, *source_range);
}
Operation::GroupEnd => {}
}
}
}
@ -1153,7 +1165,7 @@ impl ExecutorContext {
self.engine.clear_queues().await;
match exec_state.build_artifact_graph(&self.engine, program).await {
Ok(_) => exec_result.map(|(_, env_ref, _)| env_ref),
Ok(_) => exec_result,
Err(err) => exec_result.and(Err(err)),
}
}

View File

@ -0,0 +1,224 @@
use kcmc::ModelingCmd;
use kittycad_modeling_cmds::{
self as kcmc,
websocket::{ModelingCmdReq, OkWebSocketResponseData},
};
use uuid::Uuid;
#[cfg(feature = "artifact-graph")]
use crate::exec::ArtifactCommand;
use crate::{
exec::{IdGenerator, KclValue},
execution::Solid,
std::Args,
ExecState, ExecutorContext, KclError, SourceRange,
};
/// Context and metadata needed to send a single modeling command.
///
/// Many functions consume Self so that the command ID isn't accidentally reused
/// among multiple modeling commands.
pub(crate) struct ModelingCmdMeta<'a> {
/// The executor context, which contains the engine.
pub ctx: &'a ExecutorContext,
/// The source range of the command, used for error reporting.
pub source_range: SourceRange,
/// The id of the command, if it has been set by the caller or generated.
id: Option<Uuid>,
}
impl<'a> ModelingCmdMeta<'a> {
pub fn new(ctx: &'a ExecutorContext, source_range: SourceRange) -> Self {
ModelingCmdMeta {
ctx,
source_range,
id: None,
}
}
pub fn with_id(ctx: &'a ExecutorContext, source_range: SourceRange, id: Uuid) -> Self {
ModelingCmdMeta {
ctx,
source_range,
id: Some(id),
}
}
pub fn from_args_id(args: &'a Args, id: Uuid) -> Self {
ModelingCmdMeta {
ctx: &args.ctx,
source_range: args.source_range,
id: Some(id),
}
}
pub fn id(&mut self, id_generator: &mut IdGenerator) -> Uuid {
if let Some(id) = self.id {
return id;
}
let id = id_generator.next_uuid();
self.id = Some(id);
id
}
}
impl<'a> From<&'a Args> for ModelingCmdMeta<'a> {
fn from(args: &'a Args) -> Self {
ModelingCmdMeta::new(&args.ctx, args.source_range)
}
}
impl ExecState {
/// Add a modeling command to the batch but don't fire it right away.
pub(crate) async fn batch_modeling_cmd(
&mut self,
mut meta: ModelingCmdMeta<'_>,
cmd: ModelingCmd,
) -> Result<(), crate::errors::KclError> {
let id = meta.id(self.id_generator());
#[cfg(feature = "artifact-graph")]
self.push_command(ArtifactCommand {
cmd_id: id,
range: meta.source_range,
command: cmd.clone(),
});
meta.ctx.engine.batch_modeling_cmd(id, meta.source_range, &cmd).await
}
/// Add multiple modeling commands to the batch but don't fire them right
/// away.
pub(crate) async fn batch_modeling_cmds(
&mut self,
meta: ModelingCmdMeta<'_>,
cmds: &[ModelingCmdReq],
) -> Result<(), crate::errors::KclError> {
#[cfg(feature = "artifact-graph")]
for cmd_req in cmds {
self.push_command(ArtifactCommand {
cmd_id: *cmd_req.cmd_id.as_ref(),
range: meta.source_range,
command: cmd_req.cmd.clone(),
});
}
meta.ctx.engine.batch_modeling_cmds(meta.source_range, cmds).await
}
/// Add a modeling command to the batch that gets executed at the end of the
/// file. This is good for something like fillet or chamfer where the engine
/// would eat the path id if we executed it right away.
pub(crate) async fn batch_end_cmd(
&mut self,
mut meta: ModelingCmdMeta<'_>,
cmd: ModelingCmd,
) -> Result<(), crate::errors::KclError> {
let id = meta.id(self.id_generator());
// TODO: The order of the tracking of these doesn't match the order that
// they're sent to the engine.
#[cfg(feature = "artifact-graph")]
self.push_command(ArtifactCommand {
cmd_id: id,
range: meta.source_range,
command: cmd.clone(),
});
meta.ctx.engine.batch_end_cmd(id, meta.source_range, &cmd).await
}
/// Send the modeling cmd and wait for the response.
pub(crate) async fn send_modeling_cmd(
&mut self,
mut meta: ModelingCmdMeta<'_>,
cmd: ModelingCmd,
) -> Result<OkWebSocketResponseData, KclError> {
let id = meta.id(self.id_generator());
#[cfg(feature = "artifact-graph")]
self.push_command(ArtifactCommand {
cmd_id: id,
range: meta.source_range,
command: cmd.clone(),
});
meta.ctx.engine.send_modeling_cmd(id, meta.source_range, &cmd).await
}
/// Send the modeling cmd async and don't wait for the response.
/// Add it to our list of async commands.
pub(crate) async fn async_modeling_cmd(
&mut self,
mut meta: ModelingCmdMeta<'_>,
cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> {
let id = meta.id(self.id_generator());
#[cfg(feature = "artifact-graph")]
self.push_command(ArtifactCommand {
cmd_id: id,
range: meta.source_range,
command: cmd.clone(),
});
meta.ctx.engine.async_modeling_cmd(id, meta.source_range, cmd).await
}
/// Force flush the batch queue.
pub(crate) async fn flush_batch(
&mut self,
meta: ModelingCmdMeta<'_>,
// Whether or not to flush the end commands as well.
// We only do this at the very end of the file.
batch_end: bool,
) -> Result<OkWebSocketResponseData, KclError> {
meta.ctx.engine.flush_batch(batch_end, meta.source_range).await
}
/// Flush just the fillets and chamfers for this specific SolidSet.
pub(crate) async fn flush_batch_for_solids(
&mut self,
meta: ModelingCmdMeta<'_>,
solids: &[Solid],
) -> Result<(), KclError> {
// Make sure we don't traverse sketches more than once.
let mut traversed_sketches = Vec::new();
// Collect all the fillet/chamfer ids for the solids.
let mut ids = Vec::new();
for solid in solids {
// We need to traverse the solids that share the same sketch.
let sketch_id = solid.sketch.id;
if !traversed_sketches.contains(&sketch_id) {
// Find all the solids on the same shared sketch.
ids.extend(
self.stack()
.walk_call_stack()
.filter(|v| matches!(v, KclValue::Solid { value } if value.sketch.id == sketch_id))
.flat_map(|v| match v {
KclValue::Solid { value } => value.get_all_edge_cut_ids(),
_ => unreachable!(),
}),
);
traversed_sketches.push(sketch_id);
}
ids.extend(solid.get_all_edge_cut_ids());
}
// We can return early if there are no fillets or chamfers.
if ids.is_empty() {
return Ok(());
}
// We want to move these fillets and chamfers from batch_end to batch so they get executed
// before what ever we call next.
for id in ids {
// Pop it off the batch_end and add it to the batch.
let Some(item) = meta.ctx.engine.batch_end().write().await.shift_remove(&id) else {
// It might be in the batch already.
continue;
};
// Add it to the batch.
meta.ctx.engine.batch().write().await.push(item);
}
// Run flush.
// Yes, we do need to actually flush the batch here, or references will fail later.
self.flush_batch(meta, false).await?;
Ok(())
}
}

View File

@ -3,7 +3,9 @@ use std::sync::Arc;
use anyhow::Result;
use indexmap::IndexMap;
#[cfg(feature = "artifact-graph")]
use kittycad_modeling_cmds::websocket::WebSocketResponse;
use kcmc::websocket::WebSocketResponse;
#[cfg(feature = "artifact-graph")]
use kittycad_modeling_cmds as kcmc;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
@ -50,6 +52,8 @@ pub(super) struct GlobalState {
pub errors: Vec<CompilationError>,
#[cfg_attr(not(feature = "artifact-graph"), allow(dead_code))]
pub artifacts: ArtifactState,
#[cfg_attr(not(all(test, feature = "artifact-graph")), expect(dead_code))]
pub root_module_artifacts: ModuleArtifactState,
}
#[cfg(feature = "artifact-graph")]
@ -77,6 +81,20 @@ pub(super) struct ArtifactState {
#[derive(Debug, Clone, Default)]
pub(super) struct ArtifactState {}
/// Artifact state for a single module.
#[cfg(all(test, feature = "artifact-graph"))]
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
pub struct ModuleArtifactState {
/// Outgoing engine commands.
pub commands: Vec<ArtifactCommand>,
/// Operations that have been performed in execution order.
pub operations: Vec<Operation>,
}
#[cfg(not(all(test, feature = "artifact-graph")))]
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
pub struct ModuleArtifactState {}
#[derive(Debug, Clone)]
pub(super) struct ModuleState {
/// The id generator for this module.
@ -96,6 +114,7 @@ pub(super) struct ModuleState {
pub settings: MetaSettings,
pub(super) explicit_length_units: bool,
pub(super) path: ModulePath,
pub artifacts: ModuleArtifactState,
}
impl ExecState {
@ -133,7 +152,7 @@ impl ExecState {
/// Convert to execution outcome when running in WebAssembly. We want to
/// reduce the amount of data that crosses the WASM boundary as much as
/// possible.
pub async fn to_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
pub async fn into_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
// Fields are opt-in so that we don't accidentally leak private internal
// state when we add more to ExecState.
ExecOutcome {
@ -142,22 +161,18 @@ impl ExecState {
#[cfg(feature = "artifact-graph")]
operations: self.global.artifacts.operations,
#[cfg(feature = "artifact-graph")]
artifact_commands: self.global.artifacts.commands,
#[cfg(feature = "artifact-graph")]
artifact_graph: self.global.artifacts.graph,
errors: self.global.errors,
default_planes: ctx.engine.get_default_planes().read().await.clone(),
}
}
pub async fn to_mock_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
pub async fn into_mock_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
ExecOutcome {
variables: self.mod_local.variables(main_ref),
#[cfg(feature = "artifact-graph")]
operations: Default::default(),
#[cfg(feature = "artifact-graph")]
artifact_commands: Default::default(),
#[cfg(feature = "artifact-graph")]
artifact_graph: Default::default(),
errors: self.global.errors,
filenames: Default::default(),
@ -188,12 +203,22 @@ impl ExecState {
}
pub(crate) fn push_op(&mut self, op: Operation) {
#[cfg(all(test, feature = "artifact-graph"))]
self.mod_local.artifacts.operations.push(op.clone());
#[cfg(feature = "artifact-graph")]
self.global.artifacts.operations.push(op);
#[cfg(not(feature = "artifact-graph"))]
drop(op);
}
#[cfg(feature = "artifact-graph")]
pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
#[cfg(all(test, feature = "artifact-graph"))]
self.mod_local.artifacts.commands.push(command);
#[cfg(not(all(test, feature = "artifact-graph")))]
drop(command);
}
pub(super) fn next_module_id(&self) -> ModuleId {
ModuleId::from_usize(self.global.path_to_source_id.len())
}
@ -241,6 +266,21 @@ impl ExecState {
self.global.module_infos.get(&id)
}
#[cfg(all(test, feature = "artifact-graph"))]
pub(crate) fn modules(&self) -> &ModuleInfoMap {
&self.global.module_infos
}
#[cfg(all(test, feature = "artifact-graph"))]
pub(crate) fn operations(&self) -> &[Operation] {
&self.global.artifacts.operations
}
#[cfg(all(test, feature = "artifact-graph"))]
pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
&self.global.root_module_artifacts
}
pub fn current_default_units(&self) -> NumericType {
NumericType::Default {
len: self.length_unit(),
@ -349,6 +389,7 @@ impl GlobalState {
path_to_source_id: Default::default(),
module_infos: Default::default(),
artifacts: Default::default(),
root_module_artifacts: Default::default(),
mod_loader: Default::default(),
errors: Default::default(),
id_to_source: Default::default(),
@ -388,6 +429,15 @@ impl ArtifactState {
}
}
impl ModuleArtifactState {
/// When self is a cached state, extend it with new state.
#[cfg(all(test, feature = "artifact-graph"))]
pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
self.commands.extend(other.commands);
self.operations.extend(other.operations);
}
}
impl ModuleState {
pub(super) fn new(path: ModulePath, memory: Arc<ProgramMemory>, module_id: Option<ModuleId>) -> Self {
ModuleState {
@ -403,6 +453,7 @@ impl ModuleState {
default_angle_units: Default::default(),
kcl_version: "0.1".to_owned(),
},
artifacts: Default::default(),
}
}

View File

@ -104,6 +104,16 @@ impl TypedPath {
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn strip_prefix(&self, base: impl AsRef<std::path::Path>) -> Result<Self, std::path::StripPrefixError> {
self.0.strip_prefix(base).map(|p| TypedPath(p.to_path_buf()))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn canonicalize(&self) -> Result<Self, std::io::Error> {
self.0.canonicalize().map(|p| TypedPath(p.to_path_buf()))
}
pub fn to_string_lossy(&self) -> String {
#[cfg(target_arch = "wasm32")]
{