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

@ -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,29 +76,65 @@ export async function executeAst({
isInterrupted: false,
}
} catch (e: any) {
let isInterrupted = false
if (e instanceof KCLError) {
// Detect if it is a force interrupt error which is not a KCL processing error.
if (
e.msg ===
'Failed to wait for promise from engine: JsValue("Force interrupt, executionIsStale, new AST requested")'
) {
isInterrupted = true
}
return {
errors: [e],
logs: [],
execState: emptyExecState(),
isInterrupted,
}
} else {
console.log(e)
return {
logs: [e],
errors: [],
execState: emptyExecState(),
isInterrupted,
}
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.
if (
e.msg ===
'Failed to wait for promise from engine: JsValue("Force interrupt, executionIsStale, new AST requested")'
) {
isInterrupted = true
}
return {
errors: [e],
logs: [],
execState: emptyExecState(),
isInterrupted,
}
} else {
console.log(e)
return {
logs: [e],
errors: [],
execState: emptyExecState(),
isInterrupted,
}
}
}

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)
}