Merge remote-tracking branch 'origin/main' into paultag/import

This commit is contained in:
Paul Tagliamonte
2025-03-31 15:46:26 -04:00
371 changed files with 15587 additions and 10455 deletions

View File

@ -6,7 +6,7 @@ use itertools::{EitherOrBoth, Itertools};
use tokio::sync::RwLock;
use crate::{
execution::{annotations, memory::Stack, EnvironmentRef, ExecState, ExecutorSettings},
execution::{annotations, memory::Stack, state::ModuleInfoMap, EnvironmentRef, ExecState, ExecutorSettings},
parsing::ast::types::{Annotation, Node, Program},
walk::Node as WalkNode,
};
@ -15,7 +15,7 @@ lazy_static::lazy_static! {
/// A static mutable lock for updating the last successful execution state for the cache.
static ref OLD_AST: Arc<RwLock<Option<OldAstState>>> = Default::default();
// The last successful run's memory. Not cleared after an unssuccessful run.
static ref PREV_MEMORY: Arc<RwLock<Option<Stack>>> = Default::default();
static ref PREV_MEMORY: Arc<RwLock<Option<(Stack, ModuleInfoMap)>>> = Default::default();
}
/// Read the old ast memory from the lock.
@ -29,12 +29,12 @@ pub(super) async fn write_old_ast(old_state: OldAstState) {
*old_ast = Some(old_state);
}
pub(crate) async fn read_old_memory() -> Option<Stack> {
pub(crate) async fn read_old_memory() -> Option<(Stack, ModuleInfoMap)> {
let old_mem = PREV_MEMORY.read().await;
old_mem.clone()
}
pub(super) async fn write_old_memory(mem: Stack) {
pub(super) async fn write_old_memory(mem: (Stack, ModuleInfoMap)) {
let mut old_mem = PREV_MEMORY.write().await;
*old_mem = Some(mem);
}
@ -97,15 +97,6 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
// If the settings are different we might need to bust the cache.
// We specifically do this before checking if they are the exact same.
if old.settings != new.settings {
// If the units are different we need to re-execute the whole thing.
if old.settings.units != new.settings.units {
return CacheResult::ReExecute {
clear_scene: true,
reapply_settings: true,
program: new.ast.clone(),
};
}
// If anything else is different we may not need to re-execute, but rather just
// run the settings again.
reapply_settings = true;
@ -424,50 +415,6 @@ shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
assert_eq!(result, CacheResult::NoAction(false));
}
// Changing the units with the exact same file should bust the cache.
#[tokio::test(flavor = "multi_thread")]
async fn test_get_changed_program_same_code_but_different_units() {
let new = r#"// Remove the end face for the extrusion.
firstSketch = startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line(end = [24, 0])
|> line(end = [0, -24])
|> line(end = [-24, 0])
|> close()
|> extrude(length = 6)
// Remove the end face for the extrusion.
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
let ExecTestResults {
program, mut exec_ctxt, ..
} = parse_execute(new).await.unwrap();
// Change the settings to cm.
exec_ctxt.settings.units = crate::UnitLength::Cm;
let result = get_changed_program(
CacheInformation {
ast: &program.ast,
settings: &Default::default(),
},
CacheInformation {
ast: &program.ast,
settings: &exec_ctxt.settings,
},
)
.await;
assert_eq!(
result,
CacheResult::ReExecute {
clear_scene: true,
reapply_settings: true,
program: program.ast
}
);
}
// Changing the grid settings with the exact same file should NOT bust the cache.
#[tokio::test(flavor = "multi_thread")]
async fn test_get_changed_program_same_code_but_different_grid_setting() {
@ -615,4 +562,42 @@ startSketchOn('XY')
}
);
}
// Removing the units settings using an annotation, when it was non-default
// units, with the exact same file should bust the cache.
#[tokio::test(flavor = "multi_thread")]
async fn test_get_changed_program_same_code_but_removed_unit_setting_using_annotation() {
let old_code = r#"@settings(defaultLengthUnit = in)
startSketchOn('XY')
"#;
let new_code = r#"
startSketchOn('XY')
"#;
let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
new_program.compute_digest();
let result = get_changed_program(
CacheInformation {
ast: &program.ast,
settings: &exec_ctxt.settings,
},
CacheInformation {
ast: &new_program.ast,
settings: &exec_ctxt.settings,
},
)
.await;
assert_eq!(
result,
CacheResult::ReExecute {
clear_scene: true,
reapply_settings: true,
program: new_program.ast
}
);
}
}

View File

@ -64,7 +64,11 @@ impl ExecutorContext {
let new_units = exec_state.length_unit();
if !self.engine.execution_kind().await.is_isolated() {
self.engine
.set_units(new_units.into(), annotation.as_source_range())
.set_units(
new_units.into(),
annotation.as_source_range(),
exec_state.id_generator(),
)
.await?;
}
} else {
@ -106,12 +110,7 @@ impl ExecutorContext {
let old_units = exec_state.length_unit();
let original_execution = self.engine.replace_execution_kind(exec_kind).await;
let mut local_state = ModuleState::new(
&self.settings,
path.std_path(),
exec_state.stack().memory.clone(),
Some(module_id),
);
let mut local_state = ModuleState::new(path.std_path(), exec_state.stack().memory.clone(), Some(module_id));
if !preserve_mem {
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
}
@ -143,7 +142,9 @@ impl ExecutorContext {
// command and we'd need to flush the batch again.
// This avoids that.
if !exec_kind.is_isolated() && new_units != old_units && *path != ModulePath::Main {
self.engine.set_units(old_units.into(), Default::default()).await?;
self.engine
.set_units(old_units.into(), Default::default(), exec_state.id_generator())
.await?;
}
self.engine.replace_execution_kind(original_execution).await;

View File

@ -2,7 +2,7 @@
use crate::execution::ModuleId;
const NAMESPACE_KCL: uuid::Uuid = uuid::uuid!("efcd6508-4ce6-4a09-8317-e6a6994a3cd7");
const NAMESPACE_KCL: uuid::Uuid = uuid::uuid!("8bda3118-75eb-58c7-a866-bef1dcb495e7");
/// A generator for ArtifactIds that can be stable across executions.
#[derive(Debug, Clone, Default, PartialEq)]

View File

@ -40,7 +40,6 @@ use crate::{
fs::FileManager,
modules::{ModuleId, ModulePath},
parsing::ast::types::{Expr, ImportPath, NodeRef},
settings::types::UnitLength,
source_range::SourceRange,
std::StdLib,
CompilationError, ExecError, ExecutionKind, KclErrorWithOutputs,
@ -266,8 +265,6 @@ pub struct ExecutorContext {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct ExecutorSettings {
/// The project-default unit to use in modeling dimensions.
pub units: UnitLength,
/// Highlight edges of 3D objects?
pub highlight_edges: bool,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
@ -288,7 +285,6 @@ pub struct ExecutorSettings {
impl Default for ExecutorSettings {
fn default() -> Self {
Self {
units: Default::default(),
highlight_edges: true,
enable_ssao: false,
show_grid: false,
@ -302,7 +298,6 @@ impl Default for ExecutorSettings {
impl From<crate::settings::types::Configuration> for ExecutorSettings {
fn from(config: crate::settings::types::Configuration) -> Self {
Self {
units: config.settings.modeling.base_unit,
highlight_edges: config.settings.modeling.highlight_edges.into(),
enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: config.settings.modeling.show_scale_grid,
@ -316,7 +311,6 @@ impl From<crate::settings::types::Configuration> for ExecutorSettings {
impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSettings {
fn from(config: crate::settings::types::project::ProjectConfiguration) -> Self {
Self {
units: config.settings.modeling.base_unit,
highlight_edges: config.settings.modeling.highlight_edges.into(),
enable_ssao: config.settings.modeling.enable_ssao.into(),
show_grid: Default::default(),
@ -330,7 +324,6 @@ impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSet
impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
fn from(modeling: crate::settings::types::ModelingSettings) -> Self {
Self {
units: modeling.base_unit,
highlight_edges: modeling.highlight_edges.into(),
enable_ssao: modeling.enable_ssao.into(),
show_grid: modeling.show_scale_grid,
@ -344,7 +337,6 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
impl From<crate::settings::types::project::ProjectModelingSettings> for ExecutorSettings {
fn from(modeling: crate::settings::types::project::ProjectModelingSettings) -> Self {
Self {
units: modeling.base_unit,
highlight_edges: modeling.highlight_edges.into(),
enable_ssao: modeling.enable_ssao.into(),
show_grid: Default::default(),
@ -492,26 +484,17 @@ impl ExecutorContext {
/// This allows for passing in `ZOO_API_TOKEN` and `ZOO_HOST` as environment
/// variables.
#[cfg(not(target_arch = "wasm32"))]
pub async fn new_with_default_client(units: UnitLength) -> Result<Self> {
pub async fn new_with_default_client() -> Result<Self> {
// Create the client.
let ctx = Self::new_with_client(
ExecutorSettings {
units,
..Default::default()
},
None,
None,
)
.await?;
let ctx = Self::new_with_client(Default::default(), None, None).await?;
Ok(ctx)
}
/// For executing unit tests.
#[cfg(not(target_arch = "wasm32"))]
pub async fn new_for_unit_test(units: UnitLength, engine_addr: Option<String>) -> Result<Self> {
pub async fn new_for_unit_test(engine_addr: Option<String>) -> Result<Self> {
let ctx = ExecutorContext::new_with_client(
ExecutorSettings {
units,
highlight_edges: true,
enable_ssao: false,
show_grid: false,
@ -545,6 +528,18 @@ impl ExecutorContext {
.await
}
pub async fn bust_cache_and_reset_scene(&self) -> Result<ExecOutcome, KclErrorWithOutputs> {
cache::bust_cache().await;
// Execute an empty program to clear and reset the scene.
// We specifically want to be returned the objects after the scene is reset.
// Like the default planes so it is easier to just execute an empty program
// after the cache is busted.
let outcome = self.run_with_caching(crate::Program::empty()).await?;
Ok(outcome)
}
async fn prepare_mem(&self, exec_state: &mut ExecState) -> Result<(), KclErrorWithOutputs> {
self.eval_prelude(exec_state, SourceRange::synthetic())
.await
@ -563,7 +558,10 @@ impl ExecutorContext {
let mut exec_state = ExecState::new(self);
if use_prev_memory {
match cache::read_old_memory().await {
Some(mem) => *exec_state.mut_stack() = mem,
Some(mem) => {
*exec_state.mut_stack() = mem.0;
exec_state.global.module_infos = mem.1;
}
None => self.prepare_mem(&mut exec_state).await?,
}
} else {
@ -581,10 +579,11 @@ impl ExecutorContext {
// memory, not to the exec_state which is not cached for mock execution.
let mut mem = exec_state.stack().clone();
let module_infos = exec_state.global.module_infos.clone();
let outcome = exec_state.to_mock_wasm_outcome(result.0).await;
mem.squash_env(result.0);
cache::write_old_memory(mem).await;
cache::write_old_memory((mem, module_infos)).await;
Ok(outcome)
}
@ -618,7 +617,7 @@ impl ExecutorContext {
if reapply_settings
&& self
.engine
.reapply_settings(&self.settings, Default::default())
.reapply_settings(&self.settings, Default::default(), old_state.id_generator())
.await
.is_err()
{
@ -636,7 +635,7 @@ impl ExecutorContext {
CacheResult::NoAction(true) => {
if self
.engine
.reapply_settings(&self.settings, Default::default())
.reapply_settings(&self.settings, Default::default(), old_state.id_generator())
.await
.is_ok()
{
@ -737,7 +736,7 @@ impl ExecutorContext {
// Re-apply the settings, in case the cache was busted.
self.engine
.reapply_settings(&self.settings, Default::default())
.reapply_settings(&self.settings, Default::default(), exec_state.id_generator())
.await
.map_err(KclErrorWithOutputs::no_outputs)?;
@ -774,7 +773,7 @@ impl ExecutorContext {
if !self.is_mock() {
let mut mem = exec_state.stack().deep_clone();
mem.restore_env(env_ref);
cache::write_old_memory(mem).await;
cache::write_old_memory((mem, exec_state.global.module_infos.clone())).await;
}
let session_data = self.engine.get_session_data().await;
Ok((env_ref, session_data))
@ -862,11 +861,6 @@ impl ExecutorContext {
Ok(())
}
/// Update the units for the executor.
pub(crate) fn update_units(&mut self, units: UnitLength) {
self.settings.units = units;
}
/// Get a snapshot of the current scene.
pub async fn prepare_snapshot(&self) -> std::result::Result<TakeSnapshot, ExecError> {
// Zoom to fit.
@ -1008,11 +1002,7 @@ mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::{
errors::{KclErrorDetails, Severity},
execution::memory::Stack,
ModuleId,
};
use crate::{errors::KclErrorDetails, execution::memory::Stack, ModuleId};
/// Convenience function to get a JSON value from memory and unwrap.
#[track_caller]
@ -1615,34 +1605,6 @@ const inInches = 2.0 * inch()"#;
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_unit_suggest() {
let src = "foo = 42";
let program = crate::Program::parse_no_errs(src).unwrap();
let ctx = ExecutorContext {
engine: Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
)),
fs: Arc::new(crate::fs::FileManager::new()),
stdlib: Arc::new(crate::std::StdLib::new()),
settings: ExecutorSettings {
units: UnitLength::Ft,
..Default::default()
},
context_type: ContextType::Mock,
};
let mut exec_state = ExecState::new(&ctx);
ctx.run(&program, &mut exec_state).await.unwrap();
let errs = exec_state.errors();
assert_eq!(errs.len(), 1, "{errs:?}");
let warn = &errs[0];
assert_eq!(warn.severity, Severity::Warning);
assert_eq!(
warn.apply_suggestion(src).unwrap(),
"@settings(defaultLengthUnit = ft)\nfoo = 42"
)
}
#[tokio::test(flavor = "multi_thread")]
async fn test_zero_param_fn() {
let ast = r#"const sigmaAllow = 35000 // psi
@ -1971,9 +1933,7 @@ let w = f() + f()
)
"#;
let ctx = crate::test_server::new_context(UnitLength::Mm, true, None)
.await
.unwrap();
let ctx = crate::test_server::new_context(true, None).await.unwrap();
let old_program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program.
@ -2026,9 +1986,7 @@ let w = f() + f()
)
"#;
let mut ctx = crate::test_server::new_context(UnitLength::Mm, true, None)
.await
.unwrap();
let mut ctx = crate::test_server::new_context(true, None).await.unwrap();
let old_program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program.
@ -2060,11 +2018,13 @@ let w = f() + f()
// Ensure the settings are as expected.
assert_eq!(settings_state, ctx.settings);
ctx.close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn mock_after_not_mock() {
let ctx = ExecutorContext::new_with_default_client(UnitLength::Mm).await.unwrap();
let ctx = ExecutorContext::new_with_default_client().await.unwrap();
let program = crate::Program::parse_no_errs("x = 2").unwrap();
let result = ctx.run_with_caching(program).await.unwrap();
assert_eq!(result.variables.get("x").unwrap().as_f64().unwrap(), 2.0);
@ -2073,6 +2033,9 @@ let w = f() + f()
let program2 = crate::Program::parse_no_errs("z = x + 1").unwrap();
let result = ctx2.run_mock(program2, true).await.unwrap();
assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0);
ctx.close().await;
ctx2.close().await;
}
#[tokio::test(flavor = "multi_thread")]

View File

@ -30,6 +30,8 @@ pub struct ExecState {
pub(super) exec_context: Option<super::ExecutorContext>,
}
pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
#[derive(Debug, Clone)]
pub(super) struct GlobalState {
/// Map from source file absolute path to module ID.
@ -37,7 +39,7 @@ pub(super) struct GlobalState {
/// Map from module ID to source file.
pub id_to_source: IndexMap<ModuleId, ModuleSource>,
/// Map from module ID to module info.
pub module_infos: IndexMap<ModuleId, ModuleInfo>,
pub module_infos: ModuleInfoMap,
/// Output map of UUIDs to artifacts.
pub artifacts: IndexMap<ArtifactId, Artifact>,
/// Output commands to allow building the artifact graph by the caller.
@ -80,7 +82,7 @@ impl ExecState {
pub fn new(exec_context: &super::ExecutorContext) -> Self {
ExecState {
global: GlobalState::new(&exec_context.settings),
mod_local: ModuleState::new(&exec_context.settings, None, ProgramMemory::new(), Default::default()),
mod_local: ModuleState::new(None, ProgramMemory::new(), Default::default()),
exec_context: Some(exec_context.clone()),
}
}
@ -90,7 +92,7 @@ impl ExecState {
*self = ExecState {
global,
mod_local: ModuleState::new(&exec_context.settings, None, ProgramMemory::new(), Default::default()),
mod_local: ModuleState::new(None, ProgramMemory::new(), Default::default()),
exec_context: Some(exec_context.clone()),
};
}
@ -291,12 +293,7 @@ impl GlobalState {
}
impl ModuleState {
pub(super) fn new(
exec_settings: &ExecutorSettings,
std_path: Option<String>,
memory: Arc<ProgramMemory>,
module_id: Option<ModuleId>,
) -> Self {
pub(super) fn new(std_path: Option<String>, memory: Arc<ProgramMemory>, module_id: Option<ModuleId>) -> Self {
ModuleState {
id_generator: IdGenerator::new(module_id),
stack: memory.new_stack(),
@ -305,14 +302,14 @@ impl ModuleState {
explicit_length_units: false,
std_path,
settings: MetaSettings {
default_length_units: exec_settings.units.into(),
default_length_units: Default::default(),
default_angle_units: Default::default(),
},
}
}
}
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct MetaSettings {