inital rework of execution (#528)

* inital rework

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

* updates

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

* update the program memory as well

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

* cleanups

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

* code

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

* updates for typing code

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

* fixing

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

* some fixes

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

* more fixes

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

* Only unselect line or move tool on escape, don't exit sketch

* Make scrollbar on toolbar smaller

* Add escape to exit sketch mode

* tidy up usestore

* clear scene on empty file

* disable sketch mode and re-execute on sketch loop close

* disable all but xy plane

* fix entering back into edit mode

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
This commit is contained in:
Jess Frazelle
2023-09-15 04:35:48 -07:00
committed by GitHub
parent 663c396128
commit e4f2e66029
22 changed files with 448 additions and 260 deletions

View File

@ -4,6 +4,7 @@ import { addLineHighlight, EditorView } from './editor/highlightextension'
import { parser_wasm } from './lang/abstractSyntaxTree'
import { Program } from './lang/abstractSyntaxTreeTypes'
import { getNodeFromPath } from './lang/queryAst'
import { enginelessExecutor } from './lib/testHelpers'
import {
ProgramMemory,
Position,
@ -13,13 +14,10 @@ import {
} from './lang/executor'
import { recast } from './lang/recast'
import { EditorSelection } from '@codemirror/state'
import {
ArtifactMap,
SourceRangeMap,
EngineCommandManager,
} from './lang/std/engineConnection'
import { EngineCommandManager } from './lang/std/engineConnection'
import { KCLError } from './lang/errors'
import { defferExecution } from 'lib/utils'
import { deferExecution } from 'lib/utils'
import { _executor } from './lang/executor'
export type Selection = {
type: 'default' | 'line-end' | 'line-mid'
@ -123,40 +121,37 @@ export interface StoreState {
setGuiMode: (guiMode: GuiModes) => void
logs: string[]
addLog: (log: string) => void
resetLogs: () => void
setLogs: (logs: string[]) => void
kclErrors: KCLError[]
addKCLError: (err: KCLError) => void
setErrors: (errors: KCLError[]) => void
resetKCLErrors: () => void
ast: Program
setAst: (ast: Program) => void
executeAst: (ast?: Program) => void
executeAstMock: (ast?: Program) => void
updateAst: (
ast: Program,
execute: boolean,
optionalParams?: {
focusPath?: PathToNode
callBack?: (ast: Program) => void
}
) => void
updateAstAsync: (ast: Program, focusPath?: PathToNode) => void
updateAstAsync: (
ast: Program,
reexecute: boolean,
focusPath?: PathToNode
) => void
code: string
defferedCode: string
setCode: (code: string) => void
defferedSetCode: (code: string) => void
deferredSetCode: (code: string) => void
executeCode: (code?: string) => void
formatCode: () => void
errorState: {
isError: boolean
error: string
}
setError: (error?: string) => void
programMemory: ProgramMemory
setProgramMemory: (programMemory: ProgramMemory) => void
isShiftDown: boolean
setIsShiftDown: (isShiftDown: boolean) => void
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
setArtifactNSourceRangeMaps: (a: {
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
}) => void
engineCommandManager?: EngineCommandManager
setEngineCommandManager: (engineCommandManager: EngineCommandManager) => void
mediaStream?: MediaStream
@ -197,10 +192,13 @@ let pendingAstUpdates: number[] = []
export const useStore = create<StoreState>()(
persist(
(set, get) => {
const setDefferedCode = defferExecution(
(code: string) => set({ defferedCode: code }),
600
)
// We defer this so that likely our ast has caught up to the code.
// If we are making changes that are not reflected in the ast, we
// should not be updating the ast.
const setDeferredCode = deferExecution((code: string) => {
set({ code })
get().executeCode(code)
}, 600)
return {
editorView: null,
setEditorView: (editorView) => {
@ -214,6 +212,22 @@ export const useStore = create<StoreState>()(
editorView.dispatch({ effects: addLineHighlight.of(selection) })
}
},
executeCode: async (code) => {
const result = await executeCode({
code: code || get().code,
lastAst: get().ast,
engineCommandManager: get().engineCommandManager,
})
if (!result.isChange) {
return
}
set({
ast: result.ast,
logs: result.logs,
kclErrors: result.errors,
programMemory: result.programMemory,
})
},
setCursor: (selections) => {
const { editorView } = get()
if (!editorView) return
@ -243,7 +257,10 @@ export const useStore = create<StoreState>()(
get().setCursor({
otherSelections: currestSelections.otherSelections,
codeBasedSelections: [
{ range: [0, code.length - 1], type: 'default' },
{
range: [0, code.length ? code.length - 1 : 0],
type: 'default',
},
],
})
return
@ -277,8 +294,8 @@ export const useStore = create<StoreState>()(
set((state) => ({ logs: [...state.logs, log] }))
}
},
resetLogs: () => {
set({ logs: [] })
setLogs: (logs) => {
set({ logs })
},
kclErrors: [],
addKCLError: (e) => {
@ -287,6 +304,9 @@ export const useStore = create<StoreState>()(
resetKCLErrors: () => {
set({ kclErrors: [] })
},
setErrors: (errors) => {
set({ kclErrors: errors })
},
ast: {
start: 0,
end: 0,
@ -299,7 +319,47 @@ export const useStore = create<StoreState>()(
setAst: (ast) => {
set({ ast })
},
updateAst: async (ast, { focusPath, callBack = () => {} } = {}) => {
executeAst: async (ast) => {
const _ast = ast || get().ast
if (!get().isStreamReady) return
const engineCommandManager = get().engineCommandManager!
if (!engineCommandManager) return
set({ isExecuting: true })
const { logs, errors, programMemory } = await executeAst({
ast: _ast,
engineCommandManager,
})
set({
programMemory,
logs,
kclErrors: errors,
isExecuting: false,
})
},
executeAstMock: async (ast) => {
const _ast = ast || get().ast
if (!get().isStreamReady) return
const engineCommandManager = get().engineCommandManager!
if (!engineCommandManager) return
const { logs, errors, programMemory } = await executeAst({
ast: _ast,
engineCommandManager,
useFakeExecutor: true,
})
set({
programMemory,
logs,
kclErrors: errors,
isExecuting: false,
})
},
updateAst: async (
ast,
reexecute,
{ focusPath, callBack = () => {} } = {}
) => {
const newCode = recast(ast)
const astWithUpdatedSource = parser_wasm(newCode)
callBack(astWithUpdatedSource)
@ -307,7 +367,6 @@ export const useStore = create<StoreState>()(
set({
ast: astWithUpdatedSource,
code: newCode,
defferedCode: newCode,
})
if (focusPath) {
const { node } = getNodeFromPath<any>(
@ -328,24 +387,33 @@ export const useStore = create<StoreState>()(
})
})
}
if (reexecute) {
// Call execute on the set ast.
get().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.
get().executeAstMock(astWithUpdatedSource)
}
},
updateAstAsync: async (ast, focusPath) => {
updateAstAsync: async (ast, reexecute, focusPath) => {
// clear any pending updates
pendingAstUpdates.forEach((id) => clearTimeout(id))
pendingAstUpdates = []
// setup a new update
pendingAstUpdates.push(
setTimeout(() => {
get().updateAst(ast, { focusPath })
get().updateAst(ast, reexecute, { focusPath })
}, 100) as unknown as number
)
},
code: '',
defferedCode: '',
setCode: (code) => set({ code, defferedCode: code }),
defferedSetCode: (code) => {
setCode: (code) => set({ code }),
deferredSetCode: (code) => {
set({ code })
setDefferedCode(code)
setDeferredCode(code)
},
formatCode: async () => {
const code = get().code
@ -353,20 +421,10 @@ export const useStore = create<StoreState>()(
const newCode = recast(ast)
set({ code: newCode, ast })
},
errorState: {
isError: false,
error: '',
},
setError: (error = '') => {
set({ errorState: { isError: !!error, error } })
},
programMemory: { root: {}, return: null },
setProgramMemory: (programMemory) => set({ programMemory }),
isShiftDown: false,
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
artifactMap: {},
sourceRangeMap: {},
setArtifactNSourceRangeMaps: (maps) => set({ ...maps }),
setEngineCommandManager: (engineCommandManager) =>
set({ engineCommandManager }),
setMediaStream: (mediaStream) => set({ mediaStream }),
@ -409,9 +467,165 @@ export const useStore = create<StoreState>()(
partialize: (state) =>
Object.fromEntries(
Object.entries(state).filter(([key]) =>
['code', 'defferedCode', 'openPanes'].includes(key)
['code', 'openPanes'].includes(key)
)
),
}
)
)
const defaultProgramMemory: ProgramMemory['root'] = {
_0: {
type: 'UserVal',
value: 0,
__meta: [],
},
_90: {
type: 'UserVal',
value: 90,
__meta: [],
},
_180: {
type: 'UserVal',
value: 180,
__meta: [],
},
_270: {
type: 'UserVal',
value: 270,
__meta: [],
},
PI: {
type: 'UserVal',
value: Math.PI,
__meta: [],
},
}
async function executeCode({
engineCommandManager,
code,
lastAst,
}: {
code: string
lastAst: Program
engineCommandManager?: EngineCommandManager
}): Promise<
| {
logs: string[]
errors: KCLError[]
programMemory: ProgramMemory
ast: Program
isChange: true
}
| { isChange: false }
> {
let ast: Program
try {
ast = parser_wasm(code)
} catch (e) {
let errors: KCLError[] = []
let logs: string[] = [JSON.stringify(e)]
if (e instanceof KCLError) {
errors = [e]
logs = []
if (e.msg === 'file is empty') engineCommandManager?.endSession()
}
return {
isChange: true,
logs,
errors,
programMemory: {
root: {},
return: null,
},
ast: {
start: 0,
end: 0,
body: [],
nonCodeMeta: {
noneCodeNodes: {},
start: null,
},
},
}
}
// Check if the ast we have is equal to the ast in the storage.
// If it is, we don't need to update the ast.
if (!engineCommandManager || JSON.stringify(ast) === JSON.stringify(lastAst))
return { isChange: false }
const { logs, errors, programMemory } = await executeAst({
ast,
engineCommandManager,
})
return {
ast,
logs,
errors,
programMemory,
isChange: true,
}
}
async function executeAst({
ast,
engineCommandManager,
useFakeExecutor = false,
}: {
ast: Program
engineCommandManager: EngineCommandManager
useFakeExecutor?: boolean
}): Promise<{
logs: string[]
errors: KCLError[]
programMemory: ProgramMemory
}> {
try {
if (!useFakeExecutor) {
engineCommandManager.endSession()
engineCommandManager.startNewSession()
}
const programMemory = await (useFakeExecutor
? enginelessExecutor(ast, {
root: defaultProgramMemory,
return: null,
})
: _executor(
ast,
{
root: defaultProgramMemory,
return: null,
},
engineCommandManager
))
await engineCommandManager.waitForAllCommands(ast, programMemory)
return {
logs: [],
errors: [],
programMemory,
}
} catch (e: any) {
if (e instanceof KCLError) {
return {
errors: [e],
logs: [],
programMemory: {
root: {},
return: null,
},
}
} else {
console.log(e)
return {
logs: [e],
errors: [],
programMemory: {
root: {},
return: null,
},
}
}
}
}