2025-03-15 10:08:39 -07:00
|
|
|
import {
|
2025-04-01 14:20:42 -07:00
|
|
|
errFromErrWithOutputs,
|
2025-04-01 15:31:19 -07:00
|
|
|
ExecState,
|
2025-03-15 10:08:39 -07:00
|
|
|
execStateFromRust,
|
|
|
|
initPromise,
|
2025-03-17 18:26:11 -07:00
|
|
|
mockExecStateFromRust,
|
2025-03-15 10:08:39 -07:00
|
|
|
} from 'lang/wasm'
|
2025-04-01 15:31:19 -07:00
|
|
|
import { getModule, ModuleType } from 'lib/wasm_lib_wrapper'
|
|
|
|
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
2025-04-01 14:20:42 -07:00
|
|
|
import type { Configuration } from '@rust/kcl-lib/bindings/Configuration'
|
2025-04-01 15:31:19 -07:00
|
|
|
import { DeepPartial } from 'lib/types'
|
2025-03-15 10:08:39 -07:00
|
|
|
import { Node } from '@rust/kcl-lib/bindings/Node'
|
|
|
|
import type { Program } from '@rust/kcl-lib/bindings/Program'
|
|
|
|
import { Context } from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
|
2025-04-01 15:31:19 -07:00
|
|
|
import { DefaultPlanes } from '@rust/kcl-lib/bindings/DefaultPlanes'
|
|
|
|
import { DefaultPlaneStr, defaultPlaneStrToKey } from 'lib/planes'
|
|
|
|
import { err } from 'lib/trap'
|
|
|
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
|
|
|
import { OutputFormat3d } from '@rust/kcl-lib/bindings/ModelingCmd'
|
2025-03-18 20:25:51 -07:00
|
|
|
import ModelingAppFile from './modelingAppFile'
|
2025-04-01 15:31:19 -07:00
|
|
|
import toast from 'react-hot-toast'
|
|
|
|
import { KclError as RustKclError } from '@rust/kcl-lib/bindings/KclError'
|
2025-03-15 10:08:39 -07:00
|
|
|
|
|
|
|
export default class RustContext {
|
|
|
|
private wasmInitFailed: boolean = true
|
|
|
|
private rustInstance: ModuleType | null = null
|
|
|
|
private ctxInstance: Context | null = null
|
|
|
|
private _defaultPlanes: DefaultPlanes | null = null
|
|
|
|
private engineCommandManager: EngineCommandManager
|
|
|
|
|
|
|
|
// Initialize the WASM module
|
|
|
|
async ensureWasmInit() {
|
|
|
|
try {
|
|
|
|
await initPromise
|
|
|
|
if (this.wasmInitFailed) {
|
|
|
|
this.wasmInitFailed = false
|
|
|
|
}
|
2025-03-19 03:52:10 +11:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
2025-03-15 10:08:39 -07:00
|
|
|
} catch (e) {
|
|
|
|
this.wasmInitFailed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(engineCommandManager: EngineCommandManager) {
|
|
|
|
this.engineCommandManager = engineCommandManager
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
|
|
this.ensureWasmInit().then(async () => {
|
2025-03-18 20:25:51 -07:00
|
|
|
this.ctxInstance = await this.create()
|
2025-03-15 10:08:39 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new context instance
|
2025-03-18 20:25:51 -07:00
|
|
|
async create(): Promise<Context> {
|
2025-03-15 10:08:39 -07:00
|
|
|
this.rustInstance = getModule()
|
2025-03-17 18:26:11 -07:00
|
|
|
// 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.
|
2025-03-18 20:25:51 -07:00
|
|
|
const ctxInstance = await new this.rustInstance.Context(
|
2025-03-15 10:08:39 -07:00
|
|
|
this.engineCommandManager,
|
|
|
|
fileSystemManager
|
|
|
|
)
|
2025-03-18 20:25:51 -07:00
|
|
|
|
|
|
|
return ctxInstance
|
2025-03-15 10:08:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Execute a program.
|
|
|
|
async execute(
|
|
|
|
node: Node<Program>,
|
|
|
|
settings: DeepPartial<Configuration>,
|
|
|
|
path?: string
|
|
|
|
): Promise<ExecState> {
|
2025-03-18 20:25:51 -07:00
|
|
|
const instance = await this._checkInstance()
|
2025-03-15 10:08:39 -07:00
|
|
|
|
2025-03-18 20:25:51 -07:00
|
|
|
try {
|
|
|
|
const result = await instance.execute(
|
|
|
|
JSON.stringify(node),
|
|
|
|
path,
|
|
|
|
JSON.stringify(settings)
|
|
|
|
)
|
|
|
|
/* Set the default planes, safe to call after execute. */
|
|
|
|
const outcome = execStateFromRust(result, node)
|
|
|
|
|
|
|
|
this._defaultPlanes = outcome.defaultPlanes
|
|
|
|
|
|
|
|
// Return the result.
|
|
|
|
return outcome
|
|
|
|
} catch (e: any) {
|
|
|
|
const err = errFromErrWithOutputs(e)
|
|
|
|
this._defaultPlanes = err.defaultPlanes
|
|
|
|
return Promise.reject(err)
|
2025-03-15 10:08:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-17 18:26:11 -07:00
|
|
|
// Execute a program with in mock mode.
|
|
|
|
async executeMock(
|
|
|
|
node: Node<Program>,
|
|
|
|
settings: DeepPartial<Configuration>,
|
|
|
|
path?: string,
|
|
|
|
usePrevMemory?: boolean
|
|
|
|
): Promise<ExecState> {
|
2025-03-18 20:25:51 -07:00
|
|
|
const instance = await this._checkInstance()
|
2025-03-17 18:26:11 -07:00
|
|
|
|
2025-03-18 20:25:51 -07:00
|
|
|
if (usePrevMemory === undefined) {
|
|
|
|
usePrevMemory = true
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await instance.executeMock(
|
|
|
|
JSON.stringify(node),
|
|
|
|
path,
|
|
|
|
JSON.stringify(settings),
|
|
|
|
usePrevMemory
|
|
|
|
)
|
|
|
|
return mockExecStateFromRust(result)
|
|
|
|
} catch (e: any) {
|
|
|
|
return Promise.reject(errFromErrWithOutputs(e))
|
2025-03-17 18:26:11 -07:00
|
|
|
}
|
2025-03-18 20:25:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Export a scene to a file.
|
|
|
|
async export(
|
|
|
|
format: DeepPartial<OutputFormat3d>,
|
|
|
|
settings: DeepPartial<Configuration>,
|
|
|
|
toastId: string
|
|
|
|
): Promise<ModelingAppFile[] | undefined> {
|
|
|
|
const instance = await this._checkInstance()
|
2025-03-17 18:26:11 -07:00
|
|
|
|
2025-03-18 20:25:51 -07:00
|
|
|
try {
|
|
|
|
return await instance.export(
|
|
|
|
JSON.stringify(format),
|
|
|
|
JSON.stringify(settings)
|
|
|
|
)
|
|
|
|
} catch (e: any) {
|
|
|
|
const parsed: RustKclError = JSON.parse(e.toString())
|
|
|
|
toast.error(parsed.msg, { id: toastId })
|
|
|
|
return
|
|
|
|
}
|
2025-03-17 18:26:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async waitForAllEngineCommands() {
|
|
|
|
await this.engineCommandManager.waitForAllCommands()
|
|
|
|
}
|
|
|
|
|
2025-03-15 10:08:39 -07:00
|
|
|
get defaultPlanes() {
|
|
|
|
return this._defaultPlanes
|
|
|
|
}
|
|
|
|
|
2025-03-29 11:43:42 -07:00
|
|
|
// Clear/reset the scene and bust the cache.
|
2025-03-15 10:08:39 -07:00
|
|
|
async clearSceneAndBustCache(
|
|
|
|
settings: DeepPartial<Configuration>,
|
|
|
|
path?: string
|
2025-03-29 11:43:42 -07:00
|
|
|
): Promise<ExecState> {
|
|
|
|
const instance = await this._checkInstance()
|
|
|
|
|
2025-03-15 10:08:39 -07:00
|
|
|
const ast: Node<Program> = {
|
|
|
|
body: [],
|
|
|
|
shebang: null,
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
moduleId: 0,
|
|
|
|
nonCodeMeta: {
|
|
|
|
nonCodeNodes: {},
|
|
|
|
startNodes: [],
|
|
|
|
},
|
|
|
|
innerAttrs: [],
|
|
|
|
outerAttrs: [],
|
2025-03-20 16:23:20 +13:00
|
|
|
preComments: [],
|
|
|
|
commentStart: 0,
|
2025-03-15 10:08:39 -07:00
|
|
|
}
|
|
|
|
|
2025-03-29 11:43:42 -07:00
|
|
|
try {
|
|
|
|
const result = await instance.bustCacheAndResetScene(
|
|
|
|
JSON.stringify(settings),
|
|
|
|
path
|
|
|
|
)
|
|
|
|
/* Set the default planes, safe to call after execute. */
|
|
|
|
const outcome = execStateFromRust(result, ast)
|
|
|
|
|
|
|
|
this._defaultPlanes = outcome.defaultPlanes
|
|
|
|
|
|
|
|
// Return the result.
|
|
|
|
return outcome
|
|
|
|
} catch (e: any) {
|
|
|
|
const err = errFromErrWithOutputs(e)
|
|
|
|
this._defaultPlanes = err.defaultPlanes
|
|
|
|
return Promise.reject(err)
|
|
|
|
}
|
2025-03-15 10:08:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
getDefaultPlaneId(name: DefaultPlaneStr): string | Error {
|
|
|
|
const key = defaultPlaneStrToKey(name)
|
|
|
|
if (!this.defaultPlanes) {
|
|
|
|
return new Error('Default planes not initialized')
|
|
|
|
} else if (err(key)) {
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
return this.defaultPlanes[key]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper to check if context instance exists
|
2025-03-18 20:25:51 -07:00
|
|
|
private async _checkInstance(): Promise<Context> {
|
2025-03-15 10:08:39 -07:00
|
|
|
if (!this.ctxInstance) {
|
|
|
|
// Create the context instance.
|
2025-03-18 20:25:51 -07:00
|
|
|
this.ctxInstance = await this.create()
|
2025-03-15 10:08:39 -07:00
|
|
|
}
|
2025-03-18 20:25:51 -07:00
|
|
|
|
|
|
|
return this.ctxInstance
|
2025-03-15 10:08:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Clean up resources
|
|
|
|
destroy() {
|
|
|
|
if (this.ctxInstance) {
|
|
|
|
// In a real implementation, you might need to manually free resources
|
|
|
|
this.ctxInstance = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|