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 || {}
).filter(([_, sourceRange]) => {
return isOverlap(sourceRange, range) 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

@ -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,
},
}
}
}
}