Cleanup rust/ts interface a but more w new rustContext (#5848)

* do the rust side

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

* cleanup ts side

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

* updates

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

* typo

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-17 18:26:11 -07:00
committed by GitHub
parent e17c6e272c
commit dd1534a61d
17 changed files with 351 additions and 398 deletions

View File

@ -430,21 +430,14 @@ impl ExecutorContext {
}
#[cfg(target_arch = "wasm32")]
pub async fn new_mock(
fs_manager: crate::fs::wasm::FileSystemManager,
settings: ExecutorSettings,
) -> Result<Self, String> {
Ok(ExecutorContext {
engine: Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
.map_err(|e| format!("{:?}", e))?,
)),
fs: Arc::new(FileManager::new(fs_manager)),
pub fn new_mock(engine: Arc<Box<dyn EngineManager>>, fs: Arc<FileManager>, settings: ExecutorSettings) -> Self {
ExecutorContext {
engine,
fs,
stdlib: Arc::new(StdLib::new()),
settings,
context_type: ContextType::Mock,
})
}
}
#[cfg(not(target_arch = "wasm32"))]

View File

@ -114,6 +114,10 @@ pub mod wasm_engine {
};
}
pub mod mock_engine {
pub use crate::engine::conn_mock::EngineConnection;
}
#[cfg(not(target_arch = "wasm32"))]
pub mod native_engine {
pub use crate::engine::conn::EngineConnection;

View File

@ -10,6 +10,7 @@ use wasm_bindgen::prelude::*;
pub struct Context {
engine: Arc<Box<dyn EngineManager>>,
fs: Arc<FileManager>,
mock_engine: Arc<Box<dyn EngineManager>>,
}
#[wasm_bindgen]
@ -28,16 +29,34 @@ impl Context {
.map_err(|e| format!("{:?}", e))?,
)),
fs: Arc::new(FileManager::new(fs_manager)),
mock_engine: Arc::new(Box::new(
kcl_lib::mock_engine::EngineConnection::new()
.await
.map_err(|e| format!("{:?}", e))?,
)),
})
}
fn create_executor_ctx(&self, settings: &str, path: Option<String>) -> Result<kcl_lib::ExecutorContext, String> {
fn create_executor_ctx(
&self,
settings: &str,
path: Option<String>,
is_mock: bool,
) -> Result<kcl_lib::ExecutorContext, String> {
let config: kcl_lib::Configuration = serde_json::from_str(settings).map_err(|e| e.to_string())?;
let mut settings: kcl_lib::ExecutorSettings = config.into();
if let Some(path) = path {
settings.with_current_file(std::path::PathBuf::from(path));
}
if is_mock {
return Ok(kcl_lib::ExecutorContext::new_mock(
self.mock_engine.clone(),
self.fs.clone(),
settings.into(),
));
}
Ok(kcl_lib::ExecutorContext::new(
self.engine.clone(),
self.fs.clone(),
@ -57,7 +76,7 @@ impl Context {
let program: Program = serde_json::from_str(program_ast_json).map_err(|e| e.to_string())?;
let ctx = self.create_executor_ctx(settings, path)?;
let ctx = self.create_executor_ctx(settings, path, false)?;
match ctx.run_with_caching(program).await {
// The serde-wasm-bindgen does not work here because of weird HashMap issues.
// DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
@ -65,4 +84,26 @@ impl Context {
Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?),
}
}
/// Execute a program in mock mode.
#[wasm_bindgen(js_name = executeMock)]
pub async fn execute_mock(
&self,
program_ast_json: &str,
path: Option<String>,
settings: &str,
use_prev_memory: bool,
) -> Result<JsValue, String> {
console_error_panic_hook::set_once();
let program: Program = serde_json::from_str(program_ast_json).map_err(|e| e.to_string())?;
let ctx = self.create_executor_ctx(settings, path, true)?;
match ctx.run_mock(program, use_prev_memory).await {
// The serde-wasm-bindgen does not work here because of weird HashMap issues.
// DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
Ok(outcome) => JsValue::from_serde(&outcome).map_err(|e| e.to_string()),
Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?),
}
}
}

View File

@ -3,9 +3,13 @@
#[cfg(target_arch = "wasm32")]
mod context;
#[cfg(target_arch = "wasm32")]
mod lsp;
#[cfg(target_arch = "wasm32")]
mod wasm;
#[cfg(target_arch = "wasm32")]
pub use context::*;
#[cfg(target_arch = "wasm32")]
pub use lsp::*;
#[cfg(target_arch = "wasm32")]
pub use wasm::*;

View File

@ -0,0 +1,147 @@
//! Wasm interface for our LSP servers.
use futures::stream::TryStreamExt;
use tower_lsp::{LspService, Server};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct LspServerConfig {
into_server: js_sys::AsyncIterator,
from_server: web_sys::WritableStream,
fs: kcl_lib::wasm_engine::FileSystemManager,
}
#[wasm_bindgen]
impl LspServerConfig {
#[wasm_bindgen(constructor)]
pub fn new(
into_server: js_sys::AsyncIterator,
from_server: web_sys::WritableStream,
fs: kcl_lib::wasm_engine::FileSystemManager,
) -> Self {
Self {
into_server,
from_server,
fs,
}
}
}
/// Run the `kcl` lsp server.
//
// NOTE: we don't use web_sys::ReadableStream for input here because on the
// browser side we need to use a ReadableByteStreamController to construct it
// and so far only Chromium-based browsers support that functionality.
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
#[wasm_bindgen]
pub async fn lsp_run_kcl(config: LspServerConfig, token: String, baseurl: String) -> Result<(), JsValue> {
console_error_panic_hook::set_once();
let LspServerConfig {
into_server,
from_server,
fs,
} = config;
let executor_ctx = None;
let mut zoo_client = kittycad::Client::new(token);
zoo_client.set_base_url(baseurl.as_str());
// Check if we can send telemetry for this user.
let can_send_telemetry = match zoo_client.users().get_privacy_settings().await {
Ok(privacy_settings) => privacy_settings.can_train_on_data,
Err(err) => {
// In the case of dev we don't always have a sub set, but prod we should.
if err
.to_string()
.contains("The modeling app subscription type is missing.")
{
true
} else {
web_sys::console::warn_1(&format!("Failed to get privacy settings: {err:?}").into());
false
}
}
};
let (service, socket) = LspService::build(|client| {
kcl_lib::KclLspBackend::new_wasm(client, executor_ctx, fs, zoo_client, can_send_telemetry).unwrap()
})
.custom_method("kcl/updateUnits", kcl_lib::KclLspBackend::update_units)
.custom_method("kcl/updateCanExecute", kcl_lib::KclLspBackend::update_can_execute)
.finish();
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
let input = input
.map_ok(|value| {
value
.dyn_into::<js_sys::Uint8Array>()
.expect("could not cast stream item to Uint8Array")
.to_vec()
})
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
.into_async_read();
let output = wasm_bindgen::JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
let output = wasm_streams::WritableStream::from_raw(output);
let output = output.try_into_async_write().map_err(|err| err.0)?;
Server::new(input, output, socket).serve(service).await;
Ok(())
}
/// Run the `copilot` lsp server.
//
// NOTE: we don't use web_sys::ReadableStream for input here because on the
// browser side we need to use a ReadableByteStreamController to construct it
// and so far only Chromium-based browsers support that functionality.
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
#[wasm_bindgen]
pub async fn lsp_run_copilot(config: LspServerConfig, token: String, baseurl: String) -> Result<(), JsValue> {
console_error_panic_hook::set_once();
let LspServerConfig {
into_server,
from_server,
fs,
} = config;
let mut zoo_client = kittycad::Client::new(token);
zoo_client.set_base_url(baseurl.as_str());
let dev_mode = baseurl == "https://api.dev.zoo.dev";
let (service, socket) =
LspService::build(|client| kcl_lib::CopilotLspBackend::new_wasm(client, fs, zoo_client, dev_mode))
.custom_method("copilot/setEditorInfo", kcl_lib::CopilotLspBackend::set_editor_info)
.custom_method(
"copilot/getCompletions",
kcl_lib::CopilotLspBackend::get_completions_cycling,
)
.custom_method("copilot/notifyAccepted", kcl_lib::CopilotLspBackend::accept_completion)
.custom_method("copilot/notifyRejected", kcl_lib::CopilotLspBackend::reject_completions)
.finish();
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
let input = input
.map_ok(|value| {
value
.dyn_into::<js_sys::Uint8Array>()
.expect("could not cast stream item to Uint8Array")
.to_vec()
})
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
.into_async_read();
let output = wasm_bindgen::JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
let output = wasm_streams::WritableStream::from_raw(output);
let output = output.try_into_async_write().map_err(|err| err.0)?;
Server::new(input, output, socket).serve(service).await;
Ok(())
}

View File

@ -1,38 +1,9 @@
//! Wasm bindings for `kcl`.
use futures::stream::TryStreamExt;
use gloo_utils::format::JsValueSerdeExt;
use kcl_lib::{pretty::NumericSuffix, CoreDump, Point2d, Program};
use tower_lsp::{LspService, Server};
use wasm_bindgen::prelude::*;
// wasm_bindgen wrapper for mock execute
#[wasm_bindgen]
pub async fn execute_mock(
program_ast_json: &str,
path: Option<String>,
settings: &str,
use_prev_memory: bool,
fs_manager: kcl_lib::wasm_engine::FileSystemManager,
) -> Result<JsValue, String> {
console_error_panic_hook::set_once();
let program: Program = serde_json::from_str(program_ast_json).map_err(|e| e.to_string())?;
let config: kcl_lib::Configuration = serde_json::from_str(settings).map_err(|e| e.to_string())?;
let mut settings: kcl_lib::ExecutorSettings = config.into();
if let Some(path) = path {
settings.with_current_file(std::path::PathBuf::from(path));
}
let ctx = kcl_lib::ExecutorContext::new_mock(fs_manager, settings.into()).await?;
match ctx.run_mock(program, use_prev_memory).await {
// The serde-wasm-bindgen does not work here because of weird HashMap issues.
// DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
Ok(outcome) => JsValue::from_serde(&outcome).map_err(|e| e.to_string()),
Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?),
}
}
// wasm_bindgen wrapper for execute
#[wasm_bindgen]
pub async fn kcl_lint(program_ast_json: &str) -> Result<JsValue, JsValue> {
@ -94,148 +65,6 @@ pub fn format_number(value: f64, suffix_json: &str) -> Result<String, JsError> {
Ok(kcl_lib::pretty::format_number(value, suffix))
}
#[wasm_bindgen]
pub struct ServerConfig {
into_server: js_sys::AsyncIterator,
from_server: web_sys::WritableStream,
fs: kcl_lib::wasm_engine::FileSystemManager,
}
#[wasm_bindgen]
impl ServerConfig {
#[wasm_bindgen(constructor)]
pub fn new(
into_server: js_sys::AsyncIterator,
from_server: web_sys::WritableStream,
fs: kcl_lib::wasm_engine::FileSystemManager,
) -> Self {
Self {
into_server,
from_server,
fs,
}
}
}
/// Run the `kcl` lsp server.
//
// NOTE: we don't use web_sys::ReadableStream for input here because on the
// browser side we need to use a ReadableByteStreamController to construct it
// and so far only Chromium-based browsers support that functionality.
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
#[wasm_bindgen]
pub async fn kcl_lsp_run(config: ServerConfig, token: String, baseurl: String) -> Result<(), JsValue> {
console_error_panic_hook::set_once();
let ServerConfig {
into_server,
from_server,
fs,
} = config;
let executor_ctx = None;
let mut zoo_client = kittycad::Client::new(token);
zoo_client.set_base_url(baseurl.as_str());
// Check if we can send telemetry for this user.
let can_send_telemetry = match zoo_client.users().get_privacy_settings().await {
Ok(privacy_settings) => privacy_settings.can_train_on_data,
Err(err) => {
// In the case of dev we don't always have a sub set, but prod we should.
if err
.to_string()
.contains("The modeling app subscription type is missing.")
{
true
} else {
web_sys::console::warn_1(&format!("Failed to get privacy settings: {err:?}").into());
false
}
}
};
let (service, socket) = LspService::build(|client| {
kcl_lib::KclLspBackend::new_wasm(client, executor_ctx, fs, zoo_client, can_send_telemetry).unwrap()
})
.custom_method("kcl/updateUnits", kcl_lib::KclLspBackend::update_units)
.custom_method("kcl/updateCanExecute", kcl_lib::KclLspBackend::update_can_execute)
.finish();
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
let input = input
.map_ok(|value| {
value
.dyn_into::<js_sys::Uint8Array>()
.expect("could not cast stream item to Uint8Array")
.to_vec()
})
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
.into_async_read();
let output = wasm_bindgen::JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
let output = wasm_streams::WritableStream::from_raw(output);
let output = output.try_into_async_write().map_err(|err| err.0)?;
Server::new(input, output, socket).serve(service).await;
Ok(())
}
/// Run the `copilot` lsp server.
//
// NOTE: we don't use web_sys::ReadableStream for input here because on the
// browser side we need to use a ReadableByteStreamController to construct it
// and so far only Chromium-based browsers support that functionality.
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
#[wasm_bindgen]
pub async fn copilot_lsp_run(config: ServerConfig, token: String, baseurl: String) -> Result<(), JsValue> {
console_error_panic_hook::set_once();
let ServerConfig {
into_server,
from_server,
fs,
} = config;
let mut zoo_client = kittycad::Client::new(token);
zoo_client.set_base_url(baseurl.as_str());
let dev_mode = baseurl == "https://api.dev.zoo.dev";
let (service, socket) =
LspService::build(|client| kcl_lib::CopilotLspBackend::new_wasm(client, fs, zoo_client, dev_mode))
.custom_method("copilot/setEditorInfo", kcl_lib::CopilotLspBackend::set_editor_info)
.custom_method(
"copilot/getCompletions",
kcl_lib::CopilotLspBackend::get_completions_cycling,
)
.custom_method("copilot/notifyAccepted", kcl_lib::CopilotLspBackend::accept_completion)
.custom_method("copilot/notifyRejected", kcl_lib::CopilotLspBackend::reject_completions)
.finish();
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
let input = input
.map_ok(|value| {
value
.dyn_into::<js_sys::Uint8Array>()
.expect("could not cast stream item to Uint8Array")
.to_vec()
})
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
.into_async_read();
let output = wasm_bindgen::JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
let output = wasm_streams::WritableStream::from_raw(output);
let output = output.try_into_async_write().map_err(|err| err.0)?;
Server::new(input, output, socket).serve(service).await;
Ok(())
}
#[wasm_bindgen]
pub fn is_points_ccw(points: &[f64]) -> i32 {
console_error_panic_hook::set_once();

View File

@ -39,7 +39,7 @@ import { getConstraintInfo, getConstraintInfoKw } from 'lang/std/sketch'
import { Dialog, Popover, Transition } from '@headlessui/react'
import toast from 'react-hot-toast'
import { InstanceProps, create } from 'react-modal-promise'
import { executeAst } from 'lang/langHelpers'
import { executeAstMock } from 'lang/langHelpers'
import {
deleteSegmentFromPipeExpression,
removeSingleConstraintInfo,
@ -437,12 +437,10 @@ export async function deleteSegment({
if (err(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
modifiedAst = pResult.program
const testExecute = await executeAst({
const testExecute = await executeAstMock({
ast: modifiedAst,
engineCommandManager: engineCommandManager,
isMock: true,
rustContext,
usePrevMemory: false,
rustContext: rustContext,
})
if (testExecute.errors.length) {
toast.error('Segment tag used outside of current Sketch. Could not delete.')

View File

@ -66,7 +66,7 @@ import {
} from 'lib/singletons'
import { getNodeFromPath } from 'lang/queryAst'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { executeAst, ToolTip } from 'lang/langHelpers'
import { executeAstMock, ToolTip } from 'lang/langHelpers'
import {
createProfileStartHandle,
dashedStraight,
@ -91,7 +91,6 @@ import {
createLabeledArg,
createLiteral,
createNodeFromExprSnippet,
createObjectExpression,
createPipeExpression,
createPipeSubstitution,
createVariableDeclaration,
@ -661,11 +660,9 @@ export class SceneEntities {
if (err(prepared)) return Promise.reject(prepared)
const { truncatedAst, variableDeclarationName } = prepared
const { execState } = await executeAst({
const { execState } = await executeAstMock({
ast: truncatedAst,
engineCommandManager: this.engineCommandManager,
rustContext,
isMock: true,
})
const sketchesInfo = getSketchesInfo({
sketchNodePaths,
@ -1239,11 +1236,9 @@ export class SceneEntities {
updateRectangleSketch(sketchInit, x, y, tag)
}
const { execState } = await executeAst({
const { execState } = await executeAstMock({
ast: truncatedAst,
engineCommandManager: this.engineCommandManager,
rustContext,
isMock: true,
})
const sketch = sketchFromKclValue(execState.variables[varName], varName)
if (err(sketch)) return Promise.reject(sketch)
@ -1445,11 +1440,9 @@ export class SceneEntities {
)
}
const { execState } = await executeAst({
const { execState } = await executeAstMock({
ast: truncatedAst,
engineCommandManager: this.engineCommandManager,
rustContext,
isMock: true,
})
const sketch = sketchFromKclValue(execState.variables[varName], varName)
if (err(sketch)) return Promise.reject(sketch)
@ -1624,11 +1617,9 @@ export class SceneEntities {
modded = moddedResult.modifiedAst
}
const { execState } = await executeAst({
const { execState } = await executeAstMock({
ast: modded,
engineCommandManager: this.engineCommandManager,
rustContext,
isMock: true,
})
const sketch = sketchFromKclValue(execState.variables[varName], varName)
if (err(sketch)) return
@ -2307,11 +2298,9 @@ export class SceneEntities {
modded = moddedResult.modifiedAst
}
const { execState } = await executeAst({
const { execState } = await executeAstMock({
ast: modded,
engineCommandManager: this.engineCommandManager,
rustContext,
isMock: true,
})
const sketch = sketchFromKclValue(execState.variables[varName], varName)
if (err(sketch)) return
@ -2886,11 +2875,9 @@ export class SceneEntities {
// don't want to mod the user's code yet as they have't committed to the change yet
// plus this would be the truncated ast being recast, it would be wrong
codeManager.updateCodeEditor(code)
const { execState } = await executeAst({
const { execState } = await executeAstMock({
ast: truncatedAst,
engineCommandManager: this.engineCommandManager,
rustContext,
isMock: true,
})
const variables = execState.variables
const sketchesInfo = getSketchesInfo({

View File

@ -6,9 +6,9 @@ import {
} from '@kittycad/codemirror-lsp-client'
import { fileSystemManager } from 'lang/std/fileSystemManager'
import init, {
ServerConfig,
copilot_lsp_run,
kcl_lsp_run,
LspServerConfig,
lsp_run_copilot,
lsp_run_kcl,
} from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
import * as jsrpc from 'json-rpc-2.0'
import {
@ -30,13 +30,13 @@ const initialise = async (wasmUrl: string) => {
}
export async function copilotLspRun(
config: ServerConfig,
config: LspServerConfig,
token: string,
baseUrl: string
) {
try {
console.log('starting copilot lsp')
await copilot_lsp_run(config, token, baseUrl)
await lsp_run_copilot(config, token, baseUrl)
} catch (e: any) {
console.log('copilot lsp failed', e)
// We can't restart here because a moved value, we should do this another way.
@ -44,13 +44,13 @@ export async function copilotLspRun(
}
export async function kclLspRun(
config: ServerConfig,
config: LspServerConfig,
token: string,
baseUrl: string
) {
try {
console.log('start kcl lsp')
await kcl_lsp_run(config, token, baseUrl)
await lsp_run_kcl(config, token, baseUrl)
} catch (e: any) {
console.log('kcl lsp failed', e)
// We can't restart here because a moved value, we should do this another way.
@ -70,7 +70,7 @@ onmessage = function (event: MessageEvent) {
initialise(wasmUrl)
.then(async (instantiatedModule) => {
console.log('Worker: WASM module loaded', worker, instantiatedModule)
const config = new ServerConfig(
const config = new LspServerConfig(
intoServer,
fromServer,
fileSystemManager

View File

@ -1,4 +1,4 @@
import { executeAst, lintAst } from 'lang/langHelpers'
import { executeAst, executeAstMock, lintAst } from 'lang/langHelpers'
import { handleSelectionBatch, Selections } from 'lib/selections'
import {
KCLError,
@ -338,7 +338,7 @@ export class KclManager {
if (this.isExecuting) {
this.executeIsStale = args
// The previous execteAst will be rejected and cleaned up. The execution will be marked as stale.
// The previous executeAst will be rejected and cleaned up. The execution will be marked as stale.
// A new executeAst will start.
this.engineCommandManager.rejectAllModelingCommands(
EXECUTE_AST_INTERRUPT_ERROR_MESSAGE
@ -358,9 +358,7 @@ export class KclManager {
const { logs, errors, execState, isInterrupted } = await executeAst({
ast,
path: codeManager.currentFilePath || undefined,
engineCommandManager: this.engineCommandManager,
rustContext,
isMock: false,
})
// Program was not interrupted, setup the scene
@ -476,11 +474,9 @@ export class KclManager {
}
this._ast = { ...newAst }
const { logs, errors, execState } = await executeAst({
const { logs, errors, execState } = await executeAstMock({
ast: newAst,
engineCommandManager: this.engineCommandManager,
rustContext,
isMock: true,
})
this._logs = logs

View File

@ -1,12 +1,10 @@
import {
Program,
executeMock,
kclLint,
emptyExecState,
ExecState,
jsAppSettings,
} from 'lang/wasm'
import { EngineCommandManager } from 'lang/std/engineConnection'
import { KCLError } from 'lang/errors'
import { Diagnostic } from '@codemirror/lint'
import { Node } from '@rust/kcl-lib/bindings/Node'
@ -50,33 +48,27 @@ export const toolTips: Array<ToolTip> = [
'arcTo',
]
export async function executeAst({
ast,
path,
engineCommandManager,
isMock,
usePrevMemory,
rustContext,
}: {
ast: Node<Program>
path?: string
engineCommandManager: EngineCommandManager
rustContext: RustContext
isMock: boolean
usePrevMemory?: boolean
isInterrupted?: boolean
}): Promise<{
interface ExecutionResult {
logs: string[]
errors: KCLError[]
execState: ExecState
isInterrupted: boolean
}> {
try {
const execState = await (isMock
? executeMock(ast, usePrevMemory, path)
: rustContext.execute(ast, { settings: await jsAppSettings() }, path))
}
await engineCommandManager.waitForAllCommands()
export async function executeAst({
ast,
rustContext,
path,
}: {
ast: Node<Program>
rustContext: RustContext
path?: string
}): Promise<ExecutionResult> {
try {
const settings = { settings: await jsAppSettings() }
const execState = await rustContext.execute(ast, settings, path)
await rustContext.waitForAllEngineCommands()
return {
logs: [],
errors: [],
@ -84,6 +76,43 @@ export async function executeAst({
isInterrupted: false,
}
} catch (e: any) {
return handleExecuteError(e)
}
}
export async function executeAstMock({
ast,
rustContext,
path,
usePrevMemory,
}: {
ast: Node<Program>
rustContext: RustContext
path?: string
usePrevMemory?: boolean
}): Promise<ExecutionResult> {
try {
const settings = { settings: await jsAppSettings() }
const execState = await rustContext.executeMock(
ast,
settings,
path,
usePrevMemory
)
await rustContext.waitForAllEngineCommands()
return {
logs: [],
errors: [],
execState,
isInterrupted: false,
}
} catch (e: any) {
return handleExecuteError(e)
}
}
function handleExecuteError(e: any): ExecutionResult {
let isInterrupted = false
if (e instanceof KCLError) {
// Detect if it is a force interrupt error which is not a KCL processing error.
@ -109,7 +138,6 @@ export async function executeAst({
}
}
}
}
export async function lintAst({
ast,

View File

@ -8,7 +8,7 @@ import {
rustContext,
} from 'lib/singletons'
import { err } from 'lib/trap'
import { executeAst } from 'lang/langHelpers'
import { executeAstMock } from 'lang/langHelpers'
export const deletionErrorMessage =
'Unable to delete selection. Please edit manually in code pane.'
@ -29,11 +29,9 @@ export async function deleteSelectionPromise(
return new Error(deletionErrorMessage)
}
const testExecute = await executeAst({
const testExecute = await executeAstMock({
ast: modifiedAst,
engineCommandManager,
rustContext,
isMock: true,
rustContext: rustContext,
})
if (testExecute.errors.length) {
return new Error(deletionErrorMessage)

View File

@ -3,7 +3,6 @@ import {
parse_wasm,
recast_wasm,
format_number,
execute_mock,
kcl_lint,
is_points_ccw,
get_tangential_arc_to_info,
@ -340,7 +339,7 @@ export function execStateFromRust(
}
}
function mockExecStateFromRust(execOutcome: RustExecOutcome): ExecState {
export function mockExecStateFromRust(execOutcome: RustExecOutcome): ExecState {
return {
variables: execOutcome.variables,
operations: execOutcome.operations,
@ -404,34 +403,6 @@ export function sketchFromKclValue(
return result
}
/**
* Execute a KCL program.
* @param node The AST of the program to execute.
* @param path The full path of the file being executed. Use `null` for
* expressions that don't have a file, like expressions in the command bar.
*/
export const executeMock = async (
node: Node<Program>,
usePrevMemory?: boolean,
path?: string
): Promise<ExecState> => {
try {
if (usePrevMemory === undefined) {
usePrevMemory = true
}
const execOutcome: RustExecOutcome = await execute_mock(
JSON.stringify(node),
path,
JSON.stringify({ settings: await jsAppSettings() }),
usePrevMemory,
fileSystemManager
)
return mockExecStateFromRust(execOutcome)
} catch (e: any) {
return Promise.reject(errFromErrWithOutputs(e))
}
}
export const jsAppSettings = async () => {
let jsAppSettings = default_app_settings()
if (!TEST) {

View File

@ -1,9 +1,8 @@
import { err } from './trap'
import { engineCommandManager, rustContext } from 'lib/singletons'
import { parse, resultIsOk, VariableMap } from 'lang/wasm'
import { PrevVariable } from 'lang/queryAst'
import { executeAst } from 'lang/langHelpers'
import { parse, resultIsOk } from 'lang/wasm'
import { executeAstMock } from 'lang/langHelpers'
import { KclExpression } from './commandTypes'
import { rustContext } from './singletons'
const DUMMY_VARIABLE_NAME = '__result__'
@ -19,11 +18,9 @@ export async function getCalculatedKclExpressionValue(value: string) {
const ast = pResult.program
// Execute the program without hitting the engine
const { execState } = await executeAst({
const { execState } = await executeAstMock({
ast,
engineCommandManager,
rustContext,
isMock: true,
rustContext: rustContext,
})
// Find the variable declaration for the result

View File

@ -4,6 +4,7 @@ import {
ExecState,
execStateFromRust,
initPromise,
mockExecStateFromRust,
} from 'lang/wasm'
import { getModule, ModuleType } from 'lib/wasm_lib_wrapper'
import { fileSystemManager } from 'lang/std/fileSystemManager'
@ -48,6 +49,9 @@ export default class RustContext {
// Create a new context instance
async create() {
this.rustInstance = getModule()
// We need this await here, DO NOT REMOVE it even if your editor says it's
// unnecessary. The constructor of the module is async and it will not
// resolve if you don't await it.
this.ctxInstance = await new this.rustInstance.Context(
this.engineCommandManager,
fileSystemManager
@ -87,6 +91,41 @@ export default class RustContext {
return Promise.reject(emptyExecState())
}
// Execute a program with in mock mode.
async executeMock(
node: Node<Program>,
settings: DeepPartial<Configuration>,
path?: string,
usePrevMemory?: boolean
): Promise<ExecState> {
await this._checkInstance()
if (this.ctxInstance) {
try {
if (usePrevMemory === undefined) {
usePrevMemory = true
}
const result = await this.ctxInstance.executeMock(
JSON.stringify(node),
path,
JSON.stringify(settings),
usePrevMemory
)
return mockExecStateFromRust(result)
} catch (e: any) {
return Promise.reject(errFromErrWithOutputs(e))
}
}
// You will never get here.
return Promise.reject(emptyExecState())
}
async waitForAllEngineCommands() {
await this.engineCommandManager.waitForAllCommands()
}
get defaultPlanes() {
return this._defaultPlanes
}

View File

@ -1,87 +1,12 @@
import {
Program,
executeMock,
SourceRange,
ExecState,
VariableMap,
} from '../lang/wasm'
import { EngineCommandManager } from 'lang/std/engineConnection'
import { EngineCommand } from 'lang/std/artifactGraph'
import { Models } from '@kittycad/lib'
import { v4 as uuidv4 } from 'uuid'
import { DefaultPlanes } from '@rust/kcl-lib/bindings/DefaultPlanes'
import { err } from 'lib/trap'
import { Program, ExecState, jsAppSettings } from '../lang/wasm'
import { Node } from '@rust/kcl-lib/bindings/Node'
type WebSocketResponse = Models['WebSocketResponse_type']
const defaultPlanes: DefaultPlanes = {
xy: uuidv4(),
xz: uuidv4(),
yz: uuidv4(),
negXy: uuidv4(),
negXz: uuidv4(),
negYz: uuidv4(),
}
class MockEngineCommandManager {
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor(mockParams: {
setIsStreamReady: (isReady: boolean) => void
setMediaStream: (stream: MediaStream) => void
}) {}
startNewSession() {}
waitForAllCommands() {}
waitForReady = new Promise<void>((resolve) => resolve())
sendModelingCommand({
id,
range,
command,
}: {
id: string
range: SourceRange
command: EngineCommand
}): Promise<any> {
const response: WebSocketResponse = {
success: true,
resp: {
type: 'modeling',
data: {
modeling_response: { type: 'empty' },
},
},
}
return Promise.resolve(JSON.stringify(response))
}
async wasmGetDefaultPlanes(): Promise<string> {
return JSON.stringify(defaultPlanes)
}
sendModelingCommandFromWasm(
id: string,
rangeStr: string,
commandStr: string
): Promise<any> {
if (id === undefined) {
return Promise.reject(new Error('id is undefined'))
}
if (rangeStr === undefined) {
return Promise.reject(new Error('rangeStr is undefined'))
}
if (commandStr === undefined) {
return Promise.reject(new Error('commandStr is undefined'))
}
const command: EngineCommand = JSON.parse(commandStr)
const range: SourceRange = JSON.parse(rangeStr)
return this.sendModelingCommand({ id, range, command })
}
sendSceneCommand() {}
}
import { rustContext } from './singletons'
export async function enginelessExecutor(
ast: Node<Program>,
usePrevMemory?: boolean,
path?: string
): Promise<ExecState> {
return await executeMock(ast, usePrevMemory, path)
const settings = { settings: await jsAppSettings() }
return await rustContext.executeMock(ast, settings, path, usePrevMemory)
}

View File

@ -11,7 +11,6 @@ import {
parse_wasm as ParseWasm,
recast_wasm as RecastWasm,
format_number as FormatNumber,
execute_mock as ExecuteMock,
kcl_lint as KclLint,
is_points_ccw as IsPointsCcw,
get_tangential_arc_to_info as GetTangentialArcToInfo,
@ -55,9 +54,6 @@ export const recast_wasm: typeof RecastWasm = (...args) => {
export const format_number: typeof FormatNumber = (...args) => {
return getModule().format_number(...args)
}
export const execute_mock: typeof ExecuteMock = (...args) => {
return getModule().execute_mock(...args)
}
export const kcl_lint: typeof KclLint = (...args) => {
return getModule().kcl_lint(...args)
}