refactor code storage (#2144)

* refactor code storage

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

* typo

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

* updates

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

* for now dont do onupdate its lagging the editor

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

* way smaller delay

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

* turn abck on on update

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

* dont be fancy

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

* fix linter

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

* updates

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

* empty

* empty

* good things

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

* empty

* empty

* updates

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

* updates

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

* updates

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

* updates

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

* fixes

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

* make less flakey

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

* go abck to errors for now

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2024-04-17 20:18:07 -07:00
committed by GitHub
parent 168fed038d
commit 3f8c4e7b5a
32 changed files with 314 additions and 370 deletions

View File

@ -1,4 +1,4 @@
import { executeAst, executeCode } from 'useStore'
import { executeAst } from 'useStore'
import { Selections } from 'lib/selections'
import { KCLError } from './errors'
import { uuidv4 } from 'lib/utils'
@ -16,17 +16,10 @@ import {
SketchGroup,
ExtrudeGroup,
} from 'lang/wasm'
import { bracket } from 'lib/exampleKcl'
import { getNodeFromPath } from './queryAst'
import { Params } from 'react-router-dom'
import { isTauri } from 'lib/isTauri'
import { writeTextFile } from '@tauri-apps/plugin-fs'
import { toast } from 'react-hot-toast'
const PERSIST_CODE_TOKEN = 'persistCode'
import { codeManager } from 'lib/singletons'
export class KclManager {
private _code = bracket
private _ast: Program = {
body: [],
start: 0,
@ -44,7 +37,6 @@ export class KclManager {
private _kclErrors: KCLError[] = []
private _isExecuting = false
private _wasmInitFailed = true
private _params: Params<string> = {}
engineCommandManager: EngineCommandManager
private _defferer = deferExecution((code: string) => {
@ -62,7 +54,6 @@ export class KclManager {
}, 600)
private _isExecutingCallback: (arg: boolean) => void = () => {}
private _codeCallBack: (arg: string) => void = () => {}
private _astCallBack: (arg: Program) => void = () => {}
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
private _logsCallBack: (arg: string[]) => void = () => {}
@ -78,28 +69,6 @@ export class KclManager {
this._astCallBack(ast)
}
get code() {
return this._code
}
set code(code) {
this._code = code
this._codeCallBack(code)
if (isTauri()) {
setTimeout(() => {
// Wait one event loop to give a chance for params to be set
// Save the file to disk
this._params.id &&
writeTextFile(this._params.id, code).catch((err) => {
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
console.error('error saving file', err)
toast.error('Error saving file, please check file permissions')
})
})
} else {
safteLSSetItem(PERSIST_CODE_TOKEN, code)
}
}
get programMemory() {
return this._programMemory
}
@ -140,38 +109,15 @@ export class KclManager {
this._wasmInitFailedCallback(wasmInitFailed)
}
setParams(params: Params<string>) {
this._params = params
}
constructor(engineCommandManager: EngineCommandManager) {
this.engineCommandManager = engineCommandManager
if (isTauri()) {
this.code = ''
return
}
const storedCode = safeLSGetItem(PERSIST_CODE_TOKEN) || ''
// TODO #819 remove zustand persistence logic in a few months
// short term migration, shouldn't make a difference for tauri app users
// anyway since that's filesystem based.
const zustandStore = JSON.parse(safeLSGetItem('store') || '{}')
if (storedCode === null && zustandStore?.state?.code) {
this.code = zustandStore.state.code
safteLSSetItem(PERSIST_CODE_TOKEN, this._code)
zustandStore.state.code = ''
safteLSSetItem('store', JSON.stringify(zustandStore))
} else if (storedCode === null) {
this.code = bracket
} else {
this.code = storedCode
}
this.ensureWasmInit().then(() => {
this.ast = this.safeParse(this.code) || this.ast
this.ast = this.safeParse(codeManager.code) || this.ast
})
}
registerCallBacks({
setCode,
setProgramMemory,
setAst,
setLogs,
@ -179,7 +125,6 @@ export class KclManager {
setIsExecuting,
setWasmInitFailed,
}: {
setCode: (arg: string) => void
setProgramMemory: (arg: ProgramMemory) => void
setAst: (arg: Program) => void
setLogs: (arg: string[]) => void
@ -187,7 +132,6 @@ export class KclManager {
setIsExecuting: (arg: boolean) => void
setWasmInitFailed: (arg: boolean) => void
}) {
this._codeCallBack = setCode
this._programMemoryCallBack = setProgramMemory
this._astCallBack = setAst
this._logsCallBack = setLogs
@ -227,12 +171,11 @@ export class KclManager {
private _cancelTokens: Map<number, boolean> = new Map()
async executeAst(
ast: Program = this._ast,
updateCode = false,
executionId?: number
) {
if (!this.engineCommandManager.engineConnection?.isReady()) return
// This NEVER updates the code, if you want to update the code DO NOT add to
// this function, too many other things that don't want it exist.
// just call to codeManager from wherever you want in other files.
async executeAst(ast: Program = this._ast, executionId?: number) {
await this?.engineCommandManager?.waitForReady
const currentExecutionId = executionId || Date.now()
this._cancelTokens.set(currentExecutionId, false)
@ -253,9 +196,6 @@ export class KclManager {
this.kclErrors = errors
this.programMemory = programMemory
this.ast = { ...ast }
if (updateCode) {
this.code = recast(ast)
}
this._executeCallback()
this.engineCommandManager.addCommandLog({
type: 'execution-done',
@ -263,22 +203,24 @@ export class KclManager {
})
this._cancelTokens.delete(currentExecutionId)
}
// NOTE: this always updates the code state and editor.
// DO NOT CALL THIS from codemirror ever.
async executeAstMock(
ast: Program = this._ast,
{
updates,
}: {
updates: 'none' | 'code' | 'codeAndArtifactRanges'
updates: 'none' | 'artifactRanges'
} = { updates: 'none' }
) {
await this.ensureWasmInit()
const newCode = recast(ast)
const newAst = this.safeParse(newCode)
if (!newAst) return
codeManager.updateCodeStateEditor(newCode)
// Write the file to disk.
await codeManager.writeToFile()
await this?.engineCommandManager?.waitForReady
if (updates !== 'none') {
this.setCode(recast(ast))
}
this._ast = { ...newAst }
const { logs, errors, programMemory } = await executeAst({
@ -289,7 +231,7 @@ export class KclManager {
this._logs = logs
this._kclErrors = errors
this._programMemory = programMemory
if (updates !== 'codeAndArtifactRanges') return
if (updates !== 'artifactRanges') return
Object.entries(this.engineCommandManager.artifactMap).forEach(
([commandId, artifact]) => {
if (!artifact.pathToNode) return
@ -309,79 +251,32 @@ export class KclManager {
}
)
}
executeCode = async (code?: string, executionId?: number) => {
const currentExecutionId = executionId || Date.now()
this._cancelTokens.set(currentExecutionId, false)
if (this._cancelTokens.get(currentExecutionId)) {
this._cancelTokens.delete(currentExecutionId)
return
}
await this.ensureWasmInit()
await this?.engineCommandManager?.waitForReady
const result = await executeCode({
engineCommandManager: this.engineCommandManager,
code: code || this._code,
lastAst: this._ast,
force: false,
})
// Check the cancellation token for this execution before applying side effects
if (this._cancelTokens.get(currentExecutionId)) {
this._cancelTokens.delete(currentExecutionId)
return
}
if (!result.isChange) return
const { logs, errors, programMemory, ast } = result
enterEditMode(programMemory, this.engineCommandManager)
this.logs = logs
this.kclErrors = errors
this.programMemory = programMemory
this.ast = ast
if (code) this.code = code
this._cancelTokens.delete(currentExecutionId)
}
cancelAllExecutions() {
this._cancelTokens.forEach((_, key) => {
this._cancelTokens.set(key, true)
})
}
setCode(code: string, shouldWriteFile = true) {
if (shouldWriteFile) {
// use the normal code setter
this.code = code
return
}
this._code = code
this._codeCallBack(code)
}
setCodeAndExecute(code: string, shouldWriteFile = true) {
this.setCode(code, shouldWriteFile)
if (code.trim()) {
this._defferer(code)
return
}
this._ast = {
body: [],
start: 0,
end: 0,
nonCodeMeta: {
nonCodeNodes: {},
start: [],
},
}
this._programMemory = {
root: {},
return: null,
}
this.engineCommandManager.endSession()
executeCode(force?: boolean) {
// If we want to force it we don't want to defer it.
if (!force) return this._defferer(codeManager.code)
return this.executeAst()
}
format() {
const ast = this.safeParse(this.code)
const originalCode = codeManager.code
const ast = this.safeParse(originalCode)
if (!ast) return
this.code = recast(ast)
const code = recast(ast)
if (originalCode === code) return
// Update the code state and the editor.
codeManager.updateCodeStateEditor(code)
// Write back to the file system.
codeManager.writeToFile()
}
// There's overlapping responsibility between updateAst and executeAst.
// updateAst was added as it was used a lot before xState migration so makes the port easier.
// but should probably have think about which of the function to keep
// This always updates the code state and editor and writes to the file system.
async updateAst(
ast: Program,
execute: boolean,
@ -414,12 +309,17 @@ export class KclManager {
if (execute) {
// Call execute on the set ast.
await this.executeAst(astWithUpdatedSource, true)
// Update the code state and editor.
codeManager.updateCodeStateEditor(newCode)
// Write the file to disk.
await codeManager.writeToFile()
await this.executeAst(astWithUpdatedSource)
} else {
// When we don't re-execute, we still want to update the program
// memory with the new ast. So we will hit the mock executor
// instead.
await this.executeAstMock(astWithUpdatedSource, { updates: 'code' })
// instead..
// Execute ast mock will update the code state and editor.
await this.executeAstMock(astWithUpdatedSource)
}
return returnVal
}
@ -443,16 +343,6 @@ export class KclManager {
}
}
function safeLSGetItem(key: string) {
if (typeof window === 'undefined') return null
return localStorage?.getItem(key)
}
function safteLSSetItem(key: string, value: string) {
if (typeof window === 'undefined') return
localStorage?.setItem(key, value)
}
function enterEditMode(
programMemory: ProgramMemory,
engineCommandManager: EngineCommandManager