deterministic id generator per module (#5811)

* deterministic id generator per module

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* non

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* do not remake the planes if they are alreaady made;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* do not remake the planes if they are alreaady made;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* clippy

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-03-15 10:08:39 -07:00
committed by GitHub
parent 9c986d3aa8
commit 3f02bb2065
86 changed files with 955 additions and 918 deletions

View File

@ -1153,7 +1153,7 @@ fn find_examples(text: &str, filename: &str) -> Vec<(String, String)> {
async fn run_example(text: &str) -> Result<()> {
let program = crate::Program::parse_no_errs(text)?;
let ctx = ExecutorContext::new_with_default_client(crate::UnitLength::Mm).await?;
let mut exec_state = crate::execution::ExecState::new(&ctx.settings);
let mut exec_state = crate::execution::ExecState::new(&ctx);
ctx.run(&program, &mut exec_state).await?;
Ok(())
}

View File

@ -378,22 +378,8 @@ impl EngineManager for EngineConnection {
original
}
async fn default_planes(
&self,
id_generator: &mut IdGenerator,
source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
{
let opt = self.default_planes.read().await.as_ref().cloned();
if let Some(planes) = opt {
return Ok(planes);
}
} // drop the read lock
let new_planes = self.new_default_planes(id_generator, source_range).await?;
*self.default_planes.write().await = Some(new_planes.clone());
Ok(new_planes)
fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> {
self.default_planes.clone()
}
async fn clear_scene_post_hook(

View File

@ -30,6 +30,8 @@ pub struct EngineConnection {
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
execution_kind: Arc<RwLock<ExecutionKind>>,
/// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
}
impl EngineConnection {
@ -39,6 +41,7 @@ impl EngineConnection {
batch_end: Arc::new(RwLock::new(IndexMap::new())),
artifact_commands: Arc::new(RwLock::new(Vec::new())),
execution_kind: Default::default(),
default_planes: Default::default(),
})
}
}
@ -73,12 +76,8 @@ impl crate::engine::EngineManager for EngineConnection {
original
}
async fn default_planes(
&self,
_id_generator: &mut IdGenerator,
_source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
Ok(DefaultPlanes::default())
fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> {
self.default_planes.clone()
}
async fn clear_scene_post_hook(

View File

@ -31,12 +31,6 @@ extern "C" {
idToRangeStr: String,
) -> Result<js_sys::Promise, js_sys::Error>;
#[wasm_bindgen(method, js_name = wasmGetDefaultPlanes, catch)]
fn get_default_planes(this: &EngineCommandManager) -> Result<js_sys::Promise, js_sys::Error>;
#[wasm_bindgen(method, js_name = clearDefaultPlanes, catch)]
fn clear_default_planes(this: &EngineCommandManager) -> Result<(), js_sys::Error>;
#[wasm_bindgen(method, js_name = startNewSession, catch)]
fn start_new_session(this: &EngineCommandManager) -> Result<js_sys::Promise, js_sys::Error>;
}
@ -49,6 +43,8 @@ pub struct EngineConnection {
responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>,
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
execution_kind: Arc<RwLock<ExecutionKind>>,
/// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
}
// Safety: WebAssembly will only ever run in a single-threaded context.
@ -65,6 +61,7 @@ impl EngineConnection {
responses: Arc::new(RwLock::new(IndexMap::new())),
artifact_commands: Arc::new(RwLock::new(Vec::new())),
execution_kind: Default::default(),
default_planes: Default::default(),
})
}
@ -160,59 +157,18 @@ impl crate::engine::EngineManager for EngineConnection {
original
}
async fn default_planes(
&self,
_id_generator: &mut IdGenerator,
source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
// Get the default planes.
let promise = self.manager.get_default_planes().map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to wait for promise from get default planes: {:?}", e),
source_ranges: vec![source_range],
})
})?;
// Parse the value as a string.
let s = value.as_string().ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: format!(
"Failed to get string from response from get default planes: `{:?}`",
value
),
source_ranges: vec![source_range],
})
})?;
// Deserialize the response.
let default_planes: DefaultPlanes = serde_json::from_str(&s).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to deserialize default planes: {:?}", e),
source_ranges: vec![source_range],
})
})?;
Ok(default_planes)
fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> {
self.default_planes.clone()
}
async fn clear_scene_post_hook(
&self,
_id_generator: &mut IdGenerator,
id_generator: &mut IdGenerator,
source_range: SourceRange,
) -> Result<(), KclError> {
self.manager.clear_default_planes().map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
// Remake the default planes, since they would have been removed after the scene was cleared.
let new_planes = self.new_default_planes(id_generator, source_range).await?;
*self.default_planes.write().await = Some(new_planes);
// Start a new session.
let promise = self.manager.start_new_session().map_err(|e| {

View File

@ -95,11 +95,26 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind;
/// Get the default planes.
fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>>;
/// Get the default planes, creating them if they don't exist.
async fn default_planes(
&self,
id_generator: &mut IdGenerator,
_source_range: SourceRange,
) -> Result<DefaultPlanes, crate::errors::KclError>;
source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
{
let opt = self.get_default_planes().read().await.as_ref().cloned();
if let Some(planes) = opt {
return Ok(planes);
}
} // drop the read lock
let new_planes = self.new_default_planes(id_generator, source_range).await?;
*self.get_default_planes().write().await = Some(new_planes.clone());
Ok(new_planes)
}
/// Helpers to be called after clearing a scene.
/// (These really only apply to wasm for now).

View File

@ -4,7 +4,7 @@ use thiserror::Error;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
use crate::{
execution::{ArtifactCommand, ArtifactGraph, Operation},
execution::{ArtifactCommand, ArtifactGraph, DefaultPlanes, Operation},
lsp::IntoDiagnostic,
modules::{ModulePath, ModuleSource},
source_range::SourceRange,
@ -131,6 +131,7 @@ pub struct KclErrorWithOutputs {
pub artifact_graph: ArtifactGraph,
pub filenames: IndexMap<ModuleId, ModulePath>,
pub source_files: IndexMap<ModuleId, ModuleSource>,
pub default_planes: Option<DefaultPlanes>,
}
impl KclErrorWithOutputs {
@ -141,6 +142,7 @@ impl KclErrorWithOutputs {
artifact_graph: ArtifactGraph,
filenames: IndexMap<ModuleId, ModulePath>,
source_files: IndexMap<ModuleId, ModuleSource>,
default_planes: Option<DefaultPlanes>,
) -> Self {
Self {
error,
@ -149,6 +151,7 @@ impl KclErrorWithOutputs {
artifact_graph,
filenames,
source_files,
default_planes,
}
}
pub fn no_outputs(error: KclError) -> Self {
@ -159,6 +162,7 @@ impl KclErrorWithOutputs {
artifact_graph: Default::default(),
filenames: Default::default(),
source_files: Default::default(),
default_planes: Default::default(),
}
}
pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> {

View File

@ -94,6 +94,7 @@ impl ExecutorContext {
exec_state: &mut ExecState,
exec_kind: ExecutionKind,
preserve_mem: bool,
module_id: ModuleId,
path: &ModulePath,
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
crate::log::log(format!("enter module {path} {}", exec_state.stack()));
@ -101,7 +102,12 @@ 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());
let mut local_state = ModuleState::new(
&self.settings,
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);
}
@ -452,7 +458,7 @@ impl ExecutorContext {
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
ModuleRepr::Kcl(_, Some((env_ref, items))) => Ok((*env_ref, items.clone())),
ModuleRepr::Kcl(program, cache) => self
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
.exec_module_from_ast(program, module_id, &path, exec_state, exec_kind, source_range)
.await
.map(|(_, er, items)| {
*cache = Some((er, items.clone()));
@ -483,7 +489,7 @@ impl ExecutorContext {
let result = match &repr {
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
ModuleRepr::Kcl(program, _) => self
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
.exec_module_from_ast(program, module_id, &path, exec_state, exec_kind, source_range)
.await
.map(|(val, _, _)| val),
ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self)
@ -499,13 +505,16 @@ impl ExecutorContext {
async fn exec_module_from_ast(
&self,
program: &Node<Program>,
module_id: ModuleId,
path: &ModulePath,
exec_state: &mut ExecState,
exec_kind: ExecutionKind,
source_range: SourceRange,
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
exec_state.global.mod_loader.enter_module(path);
let result = self.exec_module_body(program, exec_state, exec_kind, false, path).await;
let result = self
.exec_module_body(program, exec_state, exec_kind, false, module_id, path)
.await;
exec_state.global.mod_loader.leave_module(path);
result.map_err(|err| {
@ -699,7 +708,7 @@ fn coerce(value: KclValue, ty: &Node<Type>, exec_state: &mut ExecState) -> Resul
meta: meta.clone(),
})?;
let id = exec_state.global.id_generator.next_uuid();
let id = exec_state.next_uuid();
let plane = Plane {
id,
artifact_id: id.into(),
@ -1966,14 +1975,16 @@ impl FunctionSource {
#[cfg(test)]
mod test {
use std::sync::Arc;
use super::*;
use crate::{
execution::{memory::Stack, parse_execute},
execution::{memory::Stack, parse_execute, ContextType},
parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
};
#[test]
fn test_assign_args_to_params() {
#[tokio::test(flavor = "multi_thread")]
async fn test_assign_args_to_params() {
// Set up a little framework for this test.
fn mem(number: usize) -> KclValue {
KclValue::Number {
@ -2084,7 +2095,16 @@ mod test {
digest: None,
});
let args = args.into_iter().map(Arg::synthetic).collect();
let mut exec_state = ExecState::new(&Default::default());
let exec_ctxt = 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: Default::default(),
context_type: ContextType::Mock,
};
let mut exec_state = ExecState::new(&exec_ctxt);
exec_state.mod_local.stack = Stack::new_for_tests();
let actual = assign_args_to_params(func_expr, args, &mut exec_state).map(|_| exec_state.mod_local.stack);
assert_eq!(

View File

@ -370,7 +370,7 @@ impl Plane {
}
pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Self {
let id = exec_state.global.id_generator.next_uuid();
let id = exec_state.next_uuid();
match value {
PlaneData::XY => Plane {
id,
@ -443,17 +443,20 @@ impl Plane {
x_axis,
y_axis,
z_axis,
} => Plane {
id,
artifact_id: id.into(),
origin,
x_axis,
y_axis,
z_axis,
value: PlaneType::Custom,
units: exec_state.length_unit(),
meta: vec![],
},
} => {
let id = exec_state.next_uuid();
Plane {
id,
artifact_id: id.into(),
origin,
x_axis,
y_axis,
z_axis,
value: PlaneType::Custom,
units: exec_state.length_unit(),
meta: vec![],
}
}
}
}

View File

@ -0,0 +1,83 @@
//! A generator for ArtifactIds that can be stable across executions.
use crate::execution::ModuleId;
const NAMESPACE_KCL: uuid::Uuid = uuid::uuid!("efcd6508-4ce6-4a09-8317-e6a6994a3cd7");
/// A generator for ArtifactIds that can be stable across executions.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct IdGenerator {
module_id: Option<ModuleId>,
next_id: u64,
}
impl IdGenerator {
pub fn new(module_id: Option<ModuleId>) -> Self {
Self { module_id, next_id: 0 }
}
pub fn next_uuid(&mut self) -> uuid::Uuid {
let next_id = self.next_id;
let next = format!(
"{} {}",
self.module_id.map(|id| id.to_string()).unwrap_or("none".to_string()),
next_id
);
let next_uuid = uuid::Uuid::new_v5(&NAMESPACE_KCL, next.as_bytes());
self.next_id += 1;
next_uuid
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_id_generator() {
let mut generator = IdGenerator::new(Some(ModuleId::default()));
let uuid1 = generator.next_uuid();
let uuid2 = generator.next_uuid();
assert_ne!(uuid1, uuid2);
}
#[test]
// Test that the same generator produces the same UUIDs.
fn test_id_generator_stable() {
let mut generator = IdGenerator::new(Some(ModuleId::default()));
let uuid1 = generator.next_uuid();
let uuid2 = generator.next_uuid();
let mut generator = IdGenerator::new(Some(ModuleId::default()));
let uuid3 = generator.next_uuid();
let uuid4 = generator.next_uuid();
assert_eq!(uuid1, uuid3);
assert_eq!(uuid2, uuid4);
}
#[test]
// Generate 20 uuids and make sure all are unique.
fn test_id_generator_unique() {
let mut generator = IdGenerator::new(Some(ModuleId::default()));
let mut uuids = Vec::new();
for _ in 0..20 {
uuids.push(generator.next_uuid());
}
for i in 0..uuids.len() {
for j in i + 1..uuids.len() {
assert_ne!(uuids[i], uuids[j]);
}
}
}
}

View File

@ -10,6 +10,7 @@ use cache::OldAstState;
pub use cache::{bust_cache, clear_mem_cache};
pub use cad_op::Operation;
pub use geometry::*;
pub use id_generator::IdGenerator;
pub(crate) use import::{
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
};
@ -25,7 +26,7 @@ use kittycad_modeling_cmds as kcmc;
pub use memory::EnvironmentRef;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub use state::{ExecState, IdGenerator, MetaSettings};
pub use state::{ExecState, MetaSettings};
use crate::{
engine::EngineManager,
@ -49,6 +50,7 @@ pub(crate) mod cache;
mod cad_op;
mod exec_ast;
mod geometry;
mod id_generator;
mod import;
pub(crate) mod kcl_value;
mod memory;
@ -72,6 +74,8 @@ pub struct ExecOutcome {
pub errors: Vec<CompilationError>,
/// File Names in module Id array index order
pub filenames: IndexMap<ModuleId, ModulePath>,
/// The default planes.
pub default_planes: Option<DefaultPlanes>,
}
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
@ -367,22 +371,14 @@ impl ExecutorContext {
}
#[cfg(target_arch = "wasm32")]
pub async fn new(
engine_manager: crate::engine::conn_wasm::EngineCommandManager,
fs_manager: crate::fs::wasm::FileSystemManager,
settings: ExecutorSettings,
) -> Result<Self, String> {
Ok(ExecutorContext {
engine: Arc::new(Box::new(
crate::engine::conn_wasm::EngineConnection::new(engine_manager)
.await
.map_err(|e| format!("{:?}", e))?,
)),
fs: Arc::new(FileManager::new(fs_manager)),
pub fn new(engine: Arc<Box<dyn EngineManager>>, fs: Arc<FileManager>, settings: ExecutorSettings) -> Self {
ExecutorContext {
engine,
fs,
stdlib: Arc::new(StdLib::new()),
settings,
context_type: ContextType::Live,
})
}
}
#[cfg(not(target_arch = "wasm32"))]
@ -499,7 +495,7 @@ impl ExecutorContext {
source_range: crate::execution::SourceRange,
) -> Result<(), KclError> {
self.engine
.clear_scene(&mut exec_state.global.id_generator, source_range)
.clear_scene(&mut exec_state.mod_local.id_generator, source_range)
.await
}
@ -518,7 +514,7 @@ impl ExecutorContext {
) -> Result<ExecOutcome, KclErrorWithOutputs> {
assert!(self.is_mock());
let mut exec_state = ExecState::new(&self.settings);
let mut exec_state = ExecState::new(self);
if use_prev_memory {
match cache::read_old_memory().await {
Some(mem) => *exec_state.mut_stack() = mem,
@ -539,7 +535,7 @@ impl ExecutorContext {
// memory, not to the exec_state which is not cached for mock execution.
let mut mem = exec_state.stack().clone();
let outcome = exec_state.to_mock_wasm_outcome(result.0);
let outcome = exec_state.to_mock_wasm_outcome(result.0).await;
mem.squash_env(result.0);
cache::write_old_memory(mem).await;
@ -607,13 +603,13 @@ impl ExecutorContext {
})
.await;
let outcome = old_state.to_wasm_outcome(result_env);
let outcome = old_state.to_wasm_outcome(result_env).await;
return Ok(outcome);
}
(true, program)
}
CacheResult::NoAction(false) => {
let outcome = old_state.to_wasm_outcome(result_env);
let outcome = old_state.to_wasm_outcome(result_env).await;
return Ok(outcome);
}
};
@ -621,7 +617,7 @@ impl ExecutorContext {
let (exec_state, preserve_mem) = if clear_scene {
// Pop the execution state, since we are starting fresh.
let mut exec_state = old_state;
exec_state.reset(&self.settings);
exec_state.reset(self);
// We don't do this in mock mode since there is no engine connection
// anyways and from the TS side we override memory and don't want to clear it.
@ -638,7 +634,7 @@ impl ExecutorContext {
(program, exec_state, preserve_mem)
} else {
let mut exec_state = ExecState::new(&self.settings);
let mut exec_state = ExecState::new(self);
self.send_clear_scene(&mut exec_state, Default::default())
.await
.map_err(KclErrorWithOutputs::no_outputs)?;
@ -663,7 +659,7 @@ impl ExecutorContext {
})
.await;
let outcome = exec_state.to_wasm_outcome(result.0);
let outcome = exec_state.to_wasm_outcome(result.0).await;
Ok(outcome)
}
@ -699,6 +695,7 @@ impl ExecutorContext {
.await
.map_err(KclErrorWithOutputs::no_outputs)?;
let default_planes = self.engine.get_default_planes().read().await.clone();
let env_ref = self
.execute_and_build_graph(&program.ast, exec_state, preserve_mem)
.await
@ -717,6 +714,7 @@ impl ExecutorContext {
exec_state.global.artifact_graph.clone(),
module_id_to_module_path,
exec_state.global.id_to_source.clone(),
default_planes,
)
})?;
@ -754,6 +752,7 @@ impl ExecutorContext {
exec_state,
ExecutionKind::Normal,
preserve_mem,
ModuleId::default(),
&ModulePath::Main,
)
.await;
@ -933,7 +932,7 @@ pub(crate) async fn parse_execute(code: &str) -> Result<ExecTestResults, KclErro
settings: Default::default(),
context_type: ContextType::Mock,
};
let mut exec_state = ExecState::new(&exec_ctxt.settings);
let mut exec_state = ExecState::new(&exec_ctxt);
let result = exec_ctxt.run(&program, &mut exec_state).await?;
Ok(ExecTestResults {
@ -1880,10 +1879,14 @@ let w = f() + f()
let old_program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program.
ctx.run_with_caching(old_program).await.unwrap();
if let Err(err) = ctx.run_with_caching(old_program).await {
let report = err.into_miette_report_with_outputs(code).unwrap();
let report = miette::Report::new(report);
panic!("Error executing program: {:?}", report);
}
// Get the id_generator from the first execution.
let id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
let id_generator = cache::read_old_ast().await.unwrap().exec_state.mod_local.id_generator;
let code = r#"sketch001 = startSketchOn(XZ)
|> startProfileAt([62.74, 206.13], %)
@ -1904,7 +1907,7 @@ let w = f() + f()
// Execute the program.
ctx.run_with_caching(program).await.unwrap();
let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.mod_local.id_generator;
assert_eq!(id_generator, new_id_generator);
}
@ -1933,7 +1936,6 @@ let w = f() + f()
// Execute the program.
ctx.run_with_caching(old_program.clone()).await.unwrap();
// Get the id_generator from the first execution.
let settings_state = cache::read_old_ast().await.unwrap().settings;
// Ensure the settings are as expected.
@ -1945,7 +1947,6 @@ let w = f() + f()
// Execute the program.
ctx.run_with_caching(old_program.clone()).await.unwrap();
// Get the id_generator from the first execution.
let settings_state = cache::read_old_ast().await.unwrap().settings;
// Ensure the settings are as expected.
@ -1957,7 +1958,6 @@ let w = f() + f()
// Execute the program.
ctx.run_with_caching(old_program).await.unwrap();
// Get the id_generator from the first execution.
let settings_state = cache::read_old_ast().await.unwrap().settings;
// Ensure the settings are as expected.

View File

@ -10,7 +10,9 @@ use uuid::Uuid;
use crate::{
errors::{KclError, KclErrorDetails, Severity},
execution::{
annotations, kcl_value,
annotations,
id_generator::IdGenerator,
kcl_value,
memory::{ProgramMemory, Stack},
Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue,
Operation, UnitAngle, UnitLen,
@ -26,12 +28,11 @@ use crate::{
pub struct ExecState {
pub(super) global: GlobalState,
pub(super) mod_local: ModuleState,
pub(super) exec_context: Option<super::ExecutorContext>,
}
#[derive(Debug, Clone)]
pub(super) struct GlobalState {
/// The stable artifact ID generator.
pub id_generator: IdGenerator,
/// Map from source file absolute path to module ID.
pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
/// Map from module ID to source file.
@ -62,6 +63,8 @@ pub(super) struct GlobalState {
#[derive(Debug, Clone)]
pub(super) struct ModuleState {
/// The id generator for this module.
pub id_generator: IdGenerator,
pub stack: Stack,
/// The current value of the pipe operator returned from the previous
/// expression. If we're not currently in a pipeline, this will be None.
@ -73,25 +76,21 @@ pub(super) struct ModuleState {
}
impl ExecState {
pub fn new(exec_settings: &ExecutorSettings) -> Self {
pub fn new(exec_context: &super::ExecutorContext) -> Self {
ExecState {
global: GlobalState::new(exec_settings),
mod_local: ModuleState::new(exec_settings, None, ProgramMemory::new()),
global: GlobalState::new(&exec_context.settings),
mod_local: ModuleState::new(&exec_context.settings, None, ProgramMemory::new(), Default::default()),
exec_context: Some(exec_context.clone()),
}
}
pub(super) fn reset(&mut self, exec_settings: &ExecutorSettings) {
let mut id_generator = self.global.id_generator.clone();
// We do not pop the ids, since we want to keep the same id generator.
// This is for the front end to keep track of the ids.
id_generator.next_id = 0;
let mut global = GlobalState::new(exec_settings);
global.id_generator = id_generator;
pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
let global = GlobalState::new(&exec_context.settings);
*self = ExecState {
global,
mod_local: ModuleState::new(exec_settings, None, ProgramMemory::new()),
mod_local: ModuleState::new(&exec_context.settings, None, ProgramMemory::new(), Default::default()),
exec_context: Some(exec_context.clone()),
};
}
@ -113,7 +112,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 fn to_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
pub async fn to_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
// Fields are opt-in so that we don't accidentally leak private internal
// state when we add more to ExecState.
ExecOutcome {
@ -132,10 +131,15 @@ impl ExecState {
.iter()
.map(|(k, v)| ((*v), k.clone()))
.collect(),
default_planes: if let Some(ctx) = &self.exec_context {
ctx.engine.get_default_planes().read().await.clone()
} else {
None
},
}
}
pub fn to_mock_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
pub async fn to_mock_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
// Fields are opt-in so that we don't accidentally leak private internal
// state when we add more to ExecState.
ExecOutcome {
@ -149,6 +153,11 @@ impl ExecState {
artifact_graph: Default::default(),
errors: self.global.errors,
filenames: Default::default(),
default_planes: if let Some(ctx) = &self.exec_context {
ctx.engine.get_default_planes().read().await.clone()
} else {
None
},
}
}
@ -160,8 +169,12 @@ impl ExecState {
&mut self.mod_local.stack
}
pub(crate) fn next_uuid(&mut self) -> Uuid {
self.global.id_generator.next_uuid()
pub fn next_uuid(&mut self) -> Uuid {
self.mod_local.id_generator.next_uuid()
}
pub fn id_generator(&mut self) -> &mut IdGenerator {
&mut self.mod_local.id_generator
}
pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
@ -241,7 +254,6 @@ impl ExecState {
impl GlobalState {
fn new(settings: &ExecutorSettings) -> Self {
let mut global = GlobalState {
id_generator: Default::default(),
path_to_source_id: Default::default(),
module_infos: Default::default(),
artifacts: Default::default(),
@ -274,8 +286,14 @@ impl GlobalState {
}
impl ModuleState {
pub(super) fn new(exec_settings: &ExecutorSettings, std_path: Option<String>, memory: Arc<ProgramMemory>) -> Self {
pub(super) fn new(
exec_settings: &ExecutorSettings,
std_path: Option<String>,
memory: Arc<ProgramMemory>,
module_id: Option<ModuleId>,
) -> Self {
ModuleState {
id_generator: IdGenerator::new(module_id),
stack: memory.new_stack(),
pipe_value: Default::default(),
module_exports: Default::default(),
@ -332,29 +350,3 @@ impl MetaSettings {
Ok(())
}
}
/// A generator for ArtifactIds that can be stable across executions.
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdGenerator {
pub(super) next_id: usize,
ids: Vec<uuid::Uuid>,
}
impl IdGenerator {
pub fn new() -> Self {
Self::default()
}
pub fn next_uuid(&mut self) -> uuid::Uuid {
if let Some(id) = self.ids.get(self.next_id) {
self.next_id += 1;
*id
} else {
let id = uuid::Uuid::new_v4();
self.ids.push(id);
self.next_id += 1;
id
}
}
}

View File

@ -8,11 +8,11 @@
#[allow(unused_macros)]
macro_rules! println {
($($rest:tt)*) => {
#[cfg(feature = "disable-println")]
#[cfg(all(feature = "disable-println", not(test)))]
{
let _ = format!($($rest)*);
}
#[cfg(not(feature = "disable-println"))]
#[cfg(any(not(feature = "disable-println"), test))]
std::println!($($rest)*)
}
}
@ -20,11 +20,11 @@ macro_rules! println {
#[allow(unused_macros)]
macro_rules! eprintln {
($($rest:tt)*) => {
#[cfg(feature = "disable-println")]
#[cfg(all(feature = "disable-println", not(test)))]
{
let _ = format!($($rest)*);
}
#[cfg(not(feature = "disable-println"))]
#[cfg(any(not(feature = "disable-println"), test))]
std::eprintln!($($rest)*)
}
}
@ -32,11 +32,11 @@ macro_rules! eprintln {
#[allow(unused_macros)]
macro_rules! print {
($($rest:tt)*) => {
#[cfg(feature = "disable-println")]
#[cfg(all(feature = "disable-println", not(test)))]
{
let _ = format!($($rest)*);
}
#[cfg(not(feature = "disable-println"))]
#[cfg(any(not(feature = "disable-println"), test))]
std::print!($($rest)*)
}
}
@ -44,11 +44,11 @@ macro_rules! print {
#[allow(unused_macros)]
macro_rules! eprint {
($($rest:tt)*) => {
#[cfg(feature = "disable-println")]
#[cfg(all(feature = "disable-println", not(test)))]
{
let _ = format!($($rest)*);
}
#[cfg(not(feature = "disable-println"))]
#[cfg(any(not(feature = "disable-println"), test))]
std::eprint!($($rest)*)
}
}
@ -108,7 +108,7 @@ pub mod wasm_engine {
pub use crate::{
coredump::wasm::{CoreDumpManager, CoreDumper},
engine::conn_wasm::{EngineCommandManager, EngineConnection},
fs::wasm::FileSystemManager,
fs::wasm::{FileManager, FileSystemManager},
};
}

View File

@ -36,7 +36,7 @@ macro_rules! logln {
}
pub(crate) use logln;
#[cfg(all(not(feature = "disable-println"), not(target_arch = "wasm32")))]
#[cfg(any(test, all(not(feature = "disable-println"), not(target_arch = "wasm32"))))]
#[inline]
fn log_inner(msg: String) {
eprintln!("{msg}");
@ -48,7 +48,7 @@ fn log_inner(msg: String) {
web_sys::console::log_1(&msg.into());
}
#[cfg(feature = "disable-println")]
#[cfg(all(feature = "disable-println", not(test)))]
#[inline]
fn log_inner(_msg: String) {}

View File

@ -36,6 +36,6 @@ async fn main() {
)
.await
.unwrap();
let mut exec_state = ExecState::new(&ctx.settings);
let mut exec_state = ExecState::new(&ctx);
ctx.run(&program, &mut exec_state).await.unwrap();
}

View File

@ -33,6 +33,12 @@ impl ModuleId {
}
}
impl std::fmt::Display for ModuleId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ModuleLoader {
/// The stack of import statements for detecting circular module imports.

View File

@ -149,7 +149,7 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) {
// due to SSI and GPU.
std::fs::write(test.output_dir.join(EXPORTED_STEP_NAME), step).unwrap();
}
let outcome = exec_state.to_wasm_outcome(env_ref);
let outcome = exec_state.to_wasm_outcome(env_ref).await;
assert_common_snapshots(
test,
outcome.operations,

View File

@ -1098,6 +1098,8 @@ async fn make_sketch_plane_from_orientation(
let hide = Some(true);
match data {
PlaneData::XY | PlaneData::NegXY | PlaneData::XZ | PlaneData::NegXZ | PlaneData::YZ | PlaneData::NegYZ => {
// TODO: ignoring the default planes here since we already created them, breaks the
// front end for the feature tree which is stupid and we should fix it.
let x_axis = match data {
PlaneData::NegXY => Point3d::new(-1.0, 0.0, 0.0),
PlaneData::NegXZ => Point3d::new(-1.0, 0.0, 0.0),

View File

@ -81,7 +81,7 @@ async fn do_execute_and_snapshot(
ctx: &ExecutorContext,
program: Program,
) -> Result<(ExecState, EnvironmentRef, image::DynamicImage), ExecErrorWithState> {
let mut exec_state = ExecState::new(&ctx.settings);
let mut exec_state = ExecState::new(ctx);
let result = ctx
.run(&program, &mut exec_state)
.await
@ -156,7 +156,7 @@ pub async fn execute_and_export_step(
ExecErrorWithState,
> {
let ctx = new_context(units, true, current_file).await?;
let mut exec_state = ExecState::new(&ctx.settings);
let mut exec_state = ExecState::new(&ctx);
let program = Program::parse_no_errs(code)
.map_err(|err| ExecErrorWithState::new(KclErrorWithOutputs::no_outputs(err).into(), exec_state.clone()))?;
let result = ctx