Deterministic artifact graph - bring back the clockwork universe (#7483)

* Change to use deterministic artifact graph

* Update output to use the new order

* Fix to clear everything when scene is cleared

* Fix lots

* Update artifact graph output for the last time

* Delete unused sorting code

* Remove unneeded cfg

* Fix to preserve top-level artifacts when there's an error

* Update output after error fix

* Add better doc comments

* Remove duplicate global operations

* Update comments

* Update ignored tests that were flaky

* Update graph for new samples after rebase

* Fix test assertion message
This commit is contained in:
Jonathan Tran
2025-06-16 13:55:24 -04:00
committed by GitHub
parent d6278cf075
commit aae34cf1e5
197 changed files with 79222 additions and 69896 deletions

View File

@ -519,6 +519,12 @@ impl ExecutorContext {
exec_state: &mut ExecState,
source_range: crate::execution::SourceRange,
) -> Result<(), KclError> {
// Ensure artifacts are cleared so that we don't accumulate them across
// runs.
exec_state.mod_local.artifacts.clear();
exec_state.global.root_module_artifacts.clear();
exec_state.global.artifacts.clear();
self.engine
.clear_scene(&mut exec_state.mod_local.id_generator, source_range)
.await
@ -650,8 +656,8 @@ impl ExecutorContext {
let (new_universe, new_universe_map) =
self.get_universe(&program, &mut new_exec_state).await?;
let clear_scene = new_universe.keys().any(|key| {
let id = new_universe[key].1;
let clear_scene = new_universe.values().any(|value| {
let id = value.1;
match (
cached_state.exec_state.get_source(id),
new_exec_state.global.get_source(id),
@ -965,11 +971,10 @@ 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());
.extend(std::mem::take(&mut exec_state.mod_local.artifacts));
self.inner_run(program, exec_state, preserve_mem).await
}
@ -1114,7 +1119,7 @@ impl ExecutorContext {
// Because of execution caching, we may start with operations from a
// previous run.
#[cfg(feature = "artifact-graph")]
let start_op = exec_state.global.artifacts.operations.len();
let start_op = exec_state.global.root_module_artifacts.operations.len();
self.eval_prelude(exec_state, SourceRange::from(program).start_as_range())
.await?;
@ -1127,32 +1132,39 @@ impl ExecutorContext {
ModuleId::default(),
&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);
.await
.map(|(_, env_ref, _, module_artifacts)| {
// We need to extend because it may already have operations from
// imports.
exec_state.global.root_module_artifacts.extend(module_artifacts);
env_ref
})
.map_err(|(err, module_artifacts)| {
if let Some(module_artifacts) = module_artifacts {
// We need to extend because it may already have operations
// from imports.
exec_state.global.root_module_artifacts.extend(module_artifacts);
}
err
});
#[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) {
for op in exec_state
.global
.root_module_artifacts
.operations
.iter_mut()
.skip(start_op)
{
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);
}
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);
}
}
}
@ -1177,7 +1189,7 @@ impl ExecutorContext {
async fn eval_prelude(&self, exec_state: &mut ExecState, source_range: SourceRange) -> Result<(), KclError> {
if exec_state.stack().memory.requires_std() {
#[cfg(feature = "artifact-graph")]
let initial_ops = exec_state.global.artifacts.operations.len();
let initial_ops = exec_state.mod_local.artifacts.operations.len();
let path = vec!["std".to_owned(), "prelude".to_owned()];
let resolved_path = ModulePath::from_std_import_path(&path)?;
@ -1194,7 +1206,7 @@ impl ExecutorContext {
// TODO: Should we also clear them out of each module so that they
// don't appear in test output?
#[cfg(feature = "artifact-graph")]
exec_state.global.artifacts.operations.truncate(initial_ops);
exec_state.mod_local.artifacts.operations.truncate(initial_ops);
}
Ok(())
@ -2274,6 +2286,39 @@ w = f() + f()
ctx2.close().await;
}
#[cfg(feature = "artifact-graph")]
#[tokio::test(flavor = "multi_thread")]
async fn sim_sketch_mode_real_mock_real() {
let ctx = ExecutorContext::new_with_default_client().await.unwrap();
let code = r#"sketch001 = startSketchOn(XY)
profile001 = startProfile(sketch001, at = [0, 0])
|> line(end = [10, 0])
|> line(end = [0, 10])
|> line(end = [-10, 0])
|> line(end = [0, -10])
|> close()
"#;
let program = crate::Program::parse_no_errs(code).unwrap();
let result = ctx.run_with_caching(program).await.unwrap();
assert_eq!(result.operations.len(), 1);
let mock_ctx = ExecutorContext::new_mock(None).await;
let mock_program = crate::Program::parse_no_errs(code).unwrap();
let mock_result = mock_ctx.run_mock(mock_program, true).await.unwrap();
assert_eq!(mock_result.operations.len(), 0);
let code2 = code.to_owned()
+ r#"
extrude001 = extrude(profile001, length = 10)
"#;
let program2 = crate::Program::parse_no_errs(&code2).unwrap();
let result = ctx.run_with_caching(program2).await.unwrap();
assert_eq!(result.operations.len(), 2);
ctx.close().await;
mock_ctx.close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn read_tag_version() {
let ast = r#"fn bar(@t) {