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

@ -31,7 +31,7 @@ import { CodeMenu } from 'components/CodeMenu'
import { TextEditor } from 'components/TextEditor' import { TextEditor } from 'components/TextEditor'
import { Themes, getSystemTheme } from 'lib/theme' import { Themes, getSystemTheme } from 'lib/theme'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager' import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useCodeEval } from 'hooks/useCodeEval' import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
export function App() { export function App() {
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
@ -47,8 +47,10 @@ export function App() {
didDragInStream, didDragInStream,
streamDimensions, streamDimensions,
guiMode, guiMode,
setGuiMode,
} = useStore((s) => ({ } = useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
setCode: s.setCode, setCode: s.setCode,
engineCommandManager: s.engineCommandManager, engineCommandManager: s.engineCommandManager,
buttonDownInStream: s.buttonDownInStream, buttonDownInStream: s.buttonDownInStream,
@ -82,6 +84,38 @@ export function App() {
useHotkeys('shift + l', () => togglePane('logs')) useHotkeys('shift + l', () => togglePane('logs'))
useHotkeys('shift + e', () => togglePane('kclErrors')) useHotkeys('shift + e', () => togglePane('kclErrors'))
useHotkeys('shift + d', () => togglePane('debug')) useHotkeys('shift + d', () => togglePane('debug'))
useHotkeys('esc', () => {
if (guiMode.mode === 'sketch') {
if (guiMode.sketchMode === 'selectFace') return
if (guiMode.sketchMode === 'sketchEdit') {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
setGuiMode({ mode: 'default' })
} else {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'select',
},
})
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
rotation: guiMode.rotation,
position: guiMode.position,
pathToNode: guiMode.pathToNode,
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
})
}
} else {
setGuiMode({ mode: 'default' })
}
})
const paneOpacity = const paneOpacity =
onboardingStatus === onboardingPaths.CAMERA onboardingStatus === onboardingPaths.CAMERA
@ -105,7 +139,7 @@ export function App() {
}, [loadedCode, setCode]) }, [loadedCode, setCode])
useSetupEngineManager(streamRef, token) useSetupEngineManager(streamRef, token)
useCodeEval() useEngineConnectionSubscriptions()
const debounceSocketSend = throttle<EngineCommand>((message) => { const debounceSocketSend = throttle<EngineCommand>((message) => {
engineCommandManager?.sendSceneCommand(message) engineCommandManager?.sendSceneCommand(message)

View File

@ -47,6 +47,15 @@
@apply hover:bg-cool-20; @apply hover:bg-cool-20;
} }
.smallScrollbar::-webkit-scrollbar {
@apply h-0.5;
}
.smallScrollbar {
@apply overflow-x-auto;
scrollbar-width: thin;
}
:global(.dark) .popoverToggle { :global(.dark) .popoverToggle {
@apply hover:bg-cool-90; @apply hover:bg-cool-90;
} }

View File

@ -27,6 +27,7 @@ export const Toolbar = () => {
updateAst, updateAst,
programMemory, programMemory,
engineCommandManager, engineCommandManager,
executeAst,
} = useStore((s) => ({ } = useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
setGuiMode: s.setGuiMode, setGuiMode: s.setGuiMode,
@ -35,6 +36,7 @@ export const Toolbar = () => {
updateAst: s.updateAst, updateAst: s.updateAst,
programMemory: s.programMemory, programMemory: s.programMemory,
engineCommandManager: s.engineCommandManager, engineCommandManager: s.engineCommandManager,
executeAst: s.executeAst,
})) }))
useAppMode() useAppMode()
@ -44,7 +46,7 @@ export const Toolbar = () => {
function ToolbarButtons() { function ToolbarButtons() {
return ( return (
<span className="overflow-x-auto"> <span className={styles.smallScrollbar}>
{guiMode.mode === 'default' && ( {guiMode.mode === 'default' && (
<button <button
onClick={() => { onClick={() => {
@ -70,7 +72,7 @@ export const Toolbar = () => {
pathToNode, pathToNode,
programMemory programMemory
) )
updateAst(modifiedAst) updateAst(modifiedAst, true)
}} }}
> >
SketchOnFace SketchOnFace
@ -113,7 +115,7 @@ export const Toolbar = () => {
ast, ast,
pathToNode pathToNode
) )
updateAst(modifiedAst, { focusPath: pathToExtrudeArg }) updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}} }}
> >
ExtrudeSketch ExtrudeSketch
@ -130,7 +132,7 @@ export const Toolbar = () => {
pathToNode, pathToNode,
false false
) )
updateAst(modifiedAst, { focusPath: pathToExtrudeArg }) updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}} }}
> >
ExtrudeSketch (w/o pipe) ExtrudeSketch (w/o pipe)
@ -146,7 +148,14 @@ export const Toolbar = () => {
cmd_id: uuidv4(), cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' }, cmd: { type: 'edit_mode_exit' },
}) })
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
setGuiMode({ mode: 'default' }) setGuiMode({ mode: 'default' })
executeAst()
}} }}
> >
Exit sketch Exit sketch

View File

@ -220,9 +220,21 @@ export const Stream = ({ className = '' }) => {
) )
return return
// Check if the sketch group already exists.
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
guiMode.pathToNode,
'VariableDeclarator'
).node
const variableName = varDec?.id?.name
const sketchGroup = programMemory.root[variableName]
const isEditingExistingSketch =
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
if ( if (
resp?.data?.data?.entities_modified?.length && resp?.data?.data?.entities_modified?.length &&
guiMode.waitingFirstClick guiMode.waitingFirstClick &&
!isEditingExistingSketch
) { ) {
const curve = await engineCommandManager?.sendSceneCommand({ const curve = await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
@ -250,10 +262,10 @@ export const Stream = ({ className = '' }) => {
pathToNode: _pathToNode, pathToNode: _pathToNode,
waitingFirstClick: false, waitingFirstClick: false,
}) })
updateAst(_modifiedAst) updateAst(_modifiedAst, false)
} else if ( } else if (
resp?.data?.data?.entities_modified?.length && resp?.data?.data?.entities_modified?.length &&
!guiMode.waitingFirstClick (!guiMode.waitingFirstClick || isEditingExistingSketch)
) { ) {
const curve = await engineCommandManager?.sendSceneCommand({ const curve = await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
@ -290,6 +302,7 @@ export const Stream = ({ className = '' }) => {
fnName: 'line', fnName: 'line',
pathToNode: guiMode.pathToNode, pathToNode: guiMode.pathToNode,
}).modifiedAst }).modifiedAst
updateAst(_modifiedAst, false)
} else { } else {
_modifiedAst = addCloseToPipe({ _modifiedAst = addCloseToPipe({
node: ast, node: ast,
@ -299,8 +312,15 @@ export const Stream = ({ className = '' }) => {
setGuiMode({ setGuiMode({
mode: 'default', mode: 'default',
}) })
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_disable',
},
})
updateAst(_modifiedAst, true)
} }
updateAst(_modifiedAst)
} }
}) })
setDidDragInStream(false) setDidDragInStream(false)

View File

@ -50,7 +50,7 @@ export const TextEditor = ({
const pathParams = useParams() const pathParams = useParams()
const { const {
code, code,
defferedSetCode, deferredSetCode,
editorView, editorView,
engineCommandManager, engineCommandManager,
formatCode, formatCode,
@ -60,10 +60,9 @@ export const TextEditor = ({
setEditorView, setEditorView,
setIsLSPServerReady, setIsLSPServerReady,
setSelectionRanges, setSelectionRanges,
sourceRangeMap,
} = useStore((s) => ({ } = useStore((s) => ({
code: s.code, code: s.code,
defferedSetCode: s.defferedSetCode, deferredSetCode: s.deferredSetCode,
editorView: s.editorView, editorView: s.editorView,
engineCommandManager: s.engineCommandManager, engineCommandManager: s.engineCommandManager,
formatCode: s.formatCode, formatCode: s.formatCode,
@ -73,7 +72,6 @@ export const TextEditor = ({
setEditorView: s.setEditorView, setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady, setIsLSPServerReady: s.setIsLSPServerReady,
setSelectionRanges: s.setSelectionRanges, setSelectionRanges: s.setSelectionRanges,
sourceRangeMap: s.sourceRangeMap,
})) }))
const { const {
@ -126,7 +124,7 @@ export const TextEditor = ({
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => { // const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => { const onChange = (value: string, viewUpdate: ViewUpdate) => {
defferedSetCode(value) deferredSetCode(value)
if (isTauri() && pathParams.id) { if (isTauri() && pathParams.id) {
// Save the file to disk // Save the file to disk
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files // Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
@ -174,11 +172,11 @@ export const TextEditor = ({
) )
const idBasedSelections = codeBasedSelections const idBasedSelections = codeBasedSelections
.map(({ type, range }) => { .map(({ type, range }) => {
const hasOverlap = Object.entries(sourceRangeMap).filter( const hasOverlap = Object.entries(
([_, sourceRange]) => { engineCommandManager?.sourceRangeMap || {}
return isOverlap(sourceRange, range) ).filter(([_, sourceRange]) => {
} return isOverlap(sourceRange, range)
) })
if (hasOverlap.length) { if (hasOverlap.length) {
return { return {
type, type,

View File

@ -82,7 +82,7 @@ export const EqualAngle = () => {
transformInfos, transformInfos,
programMemory, programMemory,
}) })
updateAst(modifiedAst, { updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}

View File

@ -82,7 +82,7 @@ export const EqualLength = () => {
transformInfos, transformInfos,
programMemory, programMemory,
}) })
updateAst(modifiedAst, { updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}

View File

@ -61,7 +61,7 @@ export const HorzVert = ({
programMemory, programMemory,
referenceSegName: '', referenceSegName: '',
}) })
updateAst(modifiedAst, { updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}

View File

@ -154,7 +154,7 @@ export const Intersect = () => {
initialVariableName: 'offset', initialVariableName: 'offset',
} as any) } as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) { if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, { updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} else { } else {
@ -182,7 +182,7 @@ export const Intersect = () => {
) )
_modifiedAst.body = newBody _modifiedAst.body = newBody
} }
updateAst(_modifiedAst, { updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} }

View File

@ -65,7 +65,7 @@ export const RemoveConstrainingValues = () => {
programMemory, programMemory,
referenceSegName: '', referenceSegName: '',
}) })
updateAst(modifiedAst, { updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}

View File

@ -124,7 +124,7 @@ export const SetAbsDistance = ({
_modifiedAst.body = newBody _modifiedAst.body = newBody
} }
updateAst(_modifiedAst, { updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} catch (e) { } catch (e) {

View File

@ -113,7 +113,7 @@ export const SetAngleBetween = () => {
initialVariableName: 'angle', initialVariableName: 'angle',
} as any) } as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) { if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, { updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} else { } else {
@ -141,7 +141,7 @@ export const SetAngleBetween = () => {
) )
_modifiedAst.body = newBody _modifiedAst.body = newBody
} }
updateAst(_modifiedAst, { updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} }

View File

@ -137,7 +137,7 @@ export const SetHorzVertDistance = ({
constraint === 'setHorzDistance' ? 'xDis' : 'yDis', constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
} as any)) } as any))
if (segName === tagInfo?.tag && value === valueUsedInTransform) { if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, { updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} else { } else {
@ -163,7 +163,7 @@ export const SetHorzVertDistance = ({
) )
_modifiedAst.body = newBody _modifiedAst.body = newBody
} }
updateAst(_modifiedAst, { updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} }

View File

@ -136,7 +136,7 @@ export const SetAngleLength = ({
_modifiedAst.body = newBody _modifiedAst.body = newBody
} }
updateAst(_modifiedAst, { updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} catch (e) { } catch (e) {

View File

@ -11,8 +11,9 @@ import { isOverlap } from 'lib/utils'
interface DefaultPlanes { interface DefaultPlanes {
xy: string xy: string
yz: string // TODO re-enable
xz: string // yz: string
// xz: string
} }
export function useAppMode() { export function useAppMode() {
@ -42,24 +43,26 @@ export function useAppMode() {
y_axis: { x: 0, y: 1, z: 0 }, y_axis: { x: 0, y: 1, z: 0 },
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 }, color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
}) })
const yz = createPlane(engineCommandManager, { // TODO re-enable
x_axis: { x: 0, y: 1, z: 0 }, // const yz = createPlane(engineCommandManager, {
y_axis: { x: 0, y: 0, z: 1 }, // x_axis: { x: 0, y: 1, z: 0 },
color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 }, // y_axis: { x: 0, y: 0, z: 1 },
}) // color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
const xz = createPlane(engineCommandManager, { // })
x_axis: { x: 1, y: 0, z: 0 }, // const xz = createPlane(engineCommandManager, {
y_axis: { x: 0, y: 0, z: 1 }, // x_axis: { x: 1, y: 0, z: 0 },
color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 }, // y_axis: { x: 0, y: 0, z: 1 },
}) // color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
setDefaultPlanes({ xy, yz, xz }) // })
setDefaultPlanes({ xy })
} else { } else {
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, false) setDefaultPlanesHidden(engineCommandManager, defaultPlanes, false)
} }
} }
if (guiMode.mode !== 'sketch' && defaultPlanes) { if (guiMode.mode !== 'sketch' && defaultPlanes) {
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true) setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
} else if (guiMode.mode === 'default') { }
if (guiMode.mode === 'default') {
const pathId = const pathId =
engineCommandManager && engineCommandManager &&
isCursorInSketchCommandRange( isCursorInSketchCommandRange(
@ -220,15 +223,17 @@ function isCursorInSketchCommandRange(
([id, artifact]) => ([id, artifact]) =>
selectionRanges.codeBasedSelections.some( selectionRanges.codeBasedSelections.some(
(selection) => (selection) =>
Array.isArray(selection.range) && Array.isArray(selection?.range) &&
Array.isArray(artifact.range) && Array.isArray(artifact?.range) &&
isOverlap(selection.range, artifact.range) && isOverlap(selection.range, artifact.range) &&
(artifact.commandType === 'start_path' || (artifact.commandType === 'start_path' ||
artifact.commandType === 'extend_path' || artifact.commandType === 'extend_path' ||
'close_path') artifact.commandType === 'close_path')
) )
) )
return overlapingEntries.length === 1 && overlapingEntries[0][1].parentId return overlapingEntries.length && overlapingEntries[0][1].parentId
? overlapingEntries[0][1].parentId ? overlapingEntries[0][1].parentId
: false : overlapingEntries.find(
([, artifact]) => artifact.commandType === 'start_path'
)?.[0] || false
} }

View File

@ -1,156 +0,0 @@
import { useEffect } from 'react'
import { asyncParser } from '../lang/abstractSyntaxTree'
import { _executor } from '../lang/executor'
import { useStore } from '../useStore'
import { KCLError } from '../lang/errors'
// This recently moved out of app.tsx
// and is our old way of thinking that whenever the code changes we need to re-execute, instead of
// being more decisive about when and where we execute, its likey this custom hook will be
// refactored away entirely at some point
export function useCodeEval() {
const {
addLog,
addKCLError,
setAst,
setError,
setProgramMemory,
resetLogs,
resetKCLErrors,
setArtifactMap,
engineCommandManager,
highlightRange,
setHighlightRange,
setCursor2,
isStreamReady,
setIsExecuting,
defferedCode,
} = useStore((s) => ({
addLog: s.addLog,
defferedCode: s.defferedCode,
setAst: s.setAst,
setError: s.setError,
setProgramMemory: s.setProgramMemory,
resetLogs: s.resetLogs,
resetKCLErrors: s.resetKCLErrors,
setArtifactMap: s.setArtifactNSourceRangeMaps,
engineCommandManager: s.engineCommandManager,
highlightRange: s.highlightRange,
setHighlightRange: s.setHighlightRange,
setCursor2: s.setCursor2,
isStreamReady: s.isStreamReady,
addKCLError: s.addKCLError,
setIsExecuting: s.setIsExecuting,
}))
useEffect(() => {
if (!isStreamReady) return
if (!engineCommandManager) return
let unsubFn: any[] = []
const asyncWrap = async () => {
try {
if (!defferedCode) {
setAst({
start: 0,
end: 0,
body: [],
nonCodeMeta: {
noneCodeNodes: {},
start: null,
},
})
setProgramMemory({ root: {}, return: null })
engineCommandManager.endSession()
engineCommandManager.startNewSession()
return
}
const _ast = await asyncParser(defferedCode)
setAst(_ast)
resetLogs()
resetKCLErrors()
engineCommandManager.endSession()
engineCommandManager.startNewSession()
setIsExecuting(true)
const programMemory = await _executor(
_ast,
{
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: [],
},
},
return: null,
},
engineCommandManager
)
const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands(_ast, programMemory)
setIsExecuting(false)
if (programMemory !== undefined) {
setProgramMemory(programMemory)
}
setArtifactMap({ artifactMap, sourceRangeMap })
const unSubHover = engineCommandManager.subscribeToUnreliable({
event: 'highlight_set_entity',
callback: ({ data }) => {
if (data?.entity_id) {
const sourceRange = sourceRangeMap[data.entity_id]
setHighlightRange(sourceRange)
} else if (
!highlightRange ||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
) {
setHighlightRange([0, 0])
}
},
})
const unSubClick = engineCommandManager.subscribeTo({
event: 'select_with_point',
callback: ({ data }) => {
if (!data?.entity_id) {
setCursor2()
return
}
const sourceRange = sourceRangeMap[data.entity_id]
setCursor2({ range: sourceRange, type: 'default' })
},
})
unsubFn.push(unSubHover, unSubClick)
setError()
} catch (e: any) {
setIsExecuting(false)
if (e instanceof KCLError) {
addKCLError(e)
} else {
setError('problem')
console.log(e)
addLog(e)
}
}
}
asyncWrap()
return () => {
unsubFn.forEach((fn) => fn())
}
}, [defferedCode, isStreamReady, engineCommandManager])
}

View File

@ -0,0 +1,50 @@
import { useEffect } from 'react'
import { useStore } from 'useStore'
export function useEngineConnectionSubscriptions() {
const {
engineCommandManager,
setCursor2,
setHighlightRange,
highlightRange,
} = useStore((s) => ({
engineCommandManager: s.engineCommandManager,
setCursor2: s.setCursor2,
setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange,
}))
useEffect(() => {
if (!engineCommandManager) return
const unSubHover = engineCommandManager.subscribeToUnreliable({
event: 'highlight_set_entity',
callback: ({ data }) => {
if (data?.entity_id) {
const sourceRange =
engineCommandManager.sourceRangeMap[data.entity_id]
setHighlightRange(sourceRange)
} else if (
!highlightRange ||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
) {
setHighlightRange([0, 0])
}
},
})
const unSubClick = engineCommandManager.subscribeTo({
event: 'select_with_point',
callback: ({ data }) => {
if (!data?.entity_id) {
setCursor2()
return
}
const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id]
setCursor2({ range: sourceRange, type: 'default' })
},
})
return () => {
unSubHover()
unSubClick()
}
}, [engineCommandManager, setCursor2, setHighlightRange, highlightRange])
}

View File

@ -12,11 +12,13 @@ export function useSetupEngineManager(
setMediaStream, setMediaStream,
setIsStreamReady, setIsStreamReady,
setStreamDimensions, setStreamDimensions,
executeCode,
} = useStore((s) => ({ } = useStore((s) => ({
setEngineCommandManager: s.setEngineCommandManager, setEngineCommandManager: s.setEngineCommandManager,
setMediaStream: s.setMediaStream, setMediaStream: s.setMediaStream,
setIsStreamReady: s.setIsStreamReady, setIsStreamReady: s.setIsStreamReady,
setStreamDimensions: s.setStreamDimensions, setStreamDimensions: s.setStreamDimensions,
executeCode: s.executeCode,
})) }))
const streamWidth = streamRef?.current?.offsetWidth const streamWidth = streamRef?.current?.offsetWidth
@ -41,6 +43,9 @@ export function useSetupEngineManager(
token, token,
}) })
setEngineCommandManager(eng) setEngineCommandManager(eng)
eng.waitForReady.then(() => {
executeCode()
})
return () => { return () => {
eng?.tearDown() eng?.tearDown()
} }

View File

@ -46,7 +46,7 @@ export function useConvertToVariable() {
variableName variableName
) )
updateAst(_modifiedAst) updateAst(_modifiedAst, true)
} catch (e) { } catch (e) {
console.log('e', e) console.log('e', e)
} }

View File

@ -908,14 +908,14 @@ export class EngineCommandManager {
} }
private async fixIdMappings(ast: Program, programMemory: ProgramMemory) { private async fixIdMappings(ast: Program, programMemory: ProgramMemory) {
/* This is a temporary solution since the cmd_ids that are sent through when /* This is a temporary solution since the cmd_ids that are sent through when
sending 'extend_path' ids are not used as the segment ids. sending 'extend_path' ids are not used as the segment ids.
We have a way to back fill them with 'path_get_info', however this relies on one We have a way to back fill them with 'path_get_info', however this relies on one
the sketchGroup array and the segements array returned from the server to be in the sketchGroup array and the segements array returned from the server to be in
the same length and order. plus it's super hacky, we first use the path_id to get the same length and order. plus it's super hacky, we first use the path_id to get
the source range of the pipe expression then use the name of the variable to get the source range of the pipe expression then use the name of the variable to get
the sketchGroup from programMemory. the sketchGroup from programMemory.
I feel queezy about relying on all these steps to always line up. I feel queezy about relying on all these steps to always line up.
We have also had to pollute this EngineCommandManager class with knowledge of both the ast and programMemory We have also had to pollute this EngineCommandManager class with knowledge of both the ast and programMemory
We should get the cmd_ids to match with the segment ids and delete this method. We should get the cmd_ids to match with the segment ids and delete this method.

View File

@ -57,7 +57,7 @@ export function throttle<T>(
} }
// takes a function and executes it after the wait time, if the function is called again before the wait time is up, the timer is reset // takes a function and executes it after the wait time, if the function is called again before the wait time is up, the timer is reset
export function defferExecution<T>(func: (args: T) => any, wait: number) { export function deferExecution<T>(func: (args: T) => any, wait: number) {
let timeout: ReturnType<typeof setTimeout> | null let timeout: ReturnType<typeof setTimeout> | null
let latestArgs: T let latestArgs: T
@ -66,7 +66,7 @@ export function defferExecution<T>(func: (args: T) => any, wait: number) {
func(latestArgs) func(latestArgs)
} }
function deffered(args: T) { function deferred(args: T) {
latestArgs = args latestArgs = args
if (timeout) { if (timeout) {
clearTimeout(timeout) clearTimeout(timeout)
@ -74,7 +74,7 @@ export function defferExecution<T>(func: (args: T) => any, wait: number) {
timeout = setTimeout(later, wait) timeout = setTimeout(later, wait)
} }
return deffered return deferred
} }
export function getNormalisedCoordinates({ export function getNormalisedCoordinates({

View File

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