clear old engine ids (#415)
* clear old engine ids * animate re-execute and deffer execution for user typing
This commit is contained in:
17
src/App.tsx
17
src/App.tsx
@ -95,6 +95,9 @@ export function App() {
|
|||||||
didDragInStream,
|
didDragInStream,
|
||||||
setStreamDimensions,
|
setStreamDimensions,
|
||||||
streamDimensions,
|
streamDimensions,
|
||||||
|
setIsExecuting,
|
||||||
|
defferedCode,
|
||||||
|
defferedSetCode,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
editorView: s.editorView,
|
editorView: s.editorView,
|
||||||
setEditorView: s.setEditorView,
|
setEditorView: s.setEditorView,
|
||||||
@ -103,7 +106,9 @@ export function App() {
|
|||||||
setGuiMode: s.setGuiMode,
|
setGuiMode: s.setGuiMode,
|
||||||
addLog: s.addLog,
|
addLog: s.addLog,
|
||||||
code: s.code,
|
code: s.code,
|
||||||
|
defferedCode: s.defferedCode,
|
||||||
setCode: s.setCode,
|
setCode: s.setCode,
|
||||||
|
defferedSetCode: s.defferedSetCode,
|
||||||
setAst: s.setAst,
|
setAst: s.setAst,
|
||||||
setError: s.setError,
|
setError: s.setError,
|
||||||
setProgramMemory: s.setProgramMemory,
|
setProgramMemory: s.setProgramMemory,
|
||||||
@ -132,6 +137,7 @@ export function App() {
|
|||||||
didDragInStream: s.didDragInStream,
|
didDragInStream: s.didDragInStream,
|
||||||
setStreamDimensions: s.setStreamDimensions,
|
setStreamDimensions: s.setStreamDimensions,
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
|
setIsExecuting: s.setIsExecuting,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -182,7 +188,7 @@ export function App() {
|
|||||||
|
|
||||||
// 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) => {
|
||||||
setCode(value)
|
defferedSetCode(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
|
||||||
@ -287,16 +293,17 @@ export function App() {
|
|||||||
let unsubFn: any[] = []
|
let unsubFn: any[] = []
|
||||||
const asyncWrap = async () => {
|
const asyncWrap = async () => {
|
||||||
try {
|
try {
|
||||||
if (!code) {
|
if (!defferedCode) {
|
||||||
setAst(null)
|
setAst(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const _ast = await asyncParser(code)
|
const _ast = await asyncParser(defferedCode)
|
||||||
setAst(_ast)
|
setAst(_ast)
|
||||||
resetLogs()
|
resetLogs()
|
||||||
resetKCLErrors()
|
resetKCLErrors()
|
||||||
engineCommandManager.endSession()
|
engineCommandManager.endSession()
|
||||||
engineCommandManager.startNewSession()
|
engineCommandManager.startNewSession()
|
||||||
|
setIsExecuting(true)
|
||||||
const programMemory = await _executor(
|
const programMemory = await _executor(
|
||||||
_ast,
|
_ast,
|
||||||
{
|
{
|
||||||
@ -328,6 +335,7 @@ export function App() {
|
|||||||
|
|
||||||
const { artifactMap, sourceRangeMap } =
|
const { artifactMap, sourceRangeMap } =
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands()
|
||||||
|
setIsExecuting(false)
|
||||||
|
|
||||||
setArtifactMap({ artifactMap, sourceRangeMap })
|
setArtifactMap({ artifactMap, sourceRangeMap })
|
||||||
const unSubHover = engineCommandManager.subscribeToUnreliable({
|
const unSubHover = engineCommandManager.subscribeToUnreliable({
|
||||||
@ -362,6 +370,7 @@ export function App() {
|
|||||||
|
|
||||||
setError()
|
setError()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
setIsExecuting(false)
|
||||||
if (e instanceof KCLError) {
|
if (e instanceof KCLError) {
|
||||||
addKCLError(e)
|
addKCLError(e)
|
||||||
} else {
|
} else {
|
||||||
@ -375,7 +384,7 @@ export function App() {
|
|||||||
return () => {
|
return () => {
|
||||||
unsubFn.forEach((fn) => fn())
|
unsubFn.forEach((fn) => fn())
|
||||||
}
|
}
|
||||||
}, [code, isStreamReady, engineCommandManager])
|
}, [defferedCode, isStreamReady, engineCommandManager])
|
||||||
|
|
||||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||||
engineCommandManager?.sendSceneCommand(message)
|
engineCommandManager?.sendSceneCommand(message)
|
||||||
|
@ -21,6 +21,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
didDragInStream,
|
didDragInStream,
|
||||||
setDidDragInStream,
|
setDidDragInStream,
|
||||||
streamDimensions,
|
streamDimensions,
|
||||||
|
isExecuting,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
mediaStream: s.mediaStream,
|
mediaStream: s.mediaStream,
|
||||||
engineCommandManager: s.engineCommandManager,
|
engineCommandManager: s.engineCommandManager,
|
||||||
@ -30,6 +31,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
didDragInStream: s.didDragInStream,
|
didDragInStream: s.didDragInStream,
|
||||||
setDidDragInStream: s.setDidDragInStream,
|
setDidDragInStream: s.setDidDragInStream,
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
|
isExecuting: s.isExecuting,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -155,7 +157,8 @@ export const Stream = ({ className = '' }) => {
|
|||||||
onWheel={handleScroll}
|
onWheel={handleScroll}
|
||||||
onPlay={() => setIsLoading(false)}
|
onPlay={() => setIsLoading(false)}
|
||||||
onMouseMoveCapture={handleMouseMove}
|
onMouseMoveCapture={handleMouseMove}
|
||||||
className="w-full h-full"
|
className={`w-full h-full ${isExecuting && 'blur-md'}`}
|
||||||
|
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
||||||
/>
|
/>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="text-center absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
<div className="text-center absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||||
|
@ -10,11 +10,16 @@ import { exportSave } from 'lib/exportSave'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
|
|
||||||
interface ResultCommand {
|
interface CommandInfo {
|
||||||
|
commandType: CommandTypes
|
||||||
|
range: SourceRange
|
||||||
|
parentId?: string
|
||||||
|
}
|
||||||
|
interface ResultCommand extends CommandInfo {
|
||||||
type: 'result'
|
type: 'result'
|
||||||
data: any
|
data: any
|
||||||
}
|
}
|
||||||
interface PendingCommand {
|
interface PendingCommand extends CommandInfo {
|
||||||
type: 'pending'
|
type: 'pending'
|
||||||
promise: Promise<any>
|
promise: Promise<any>
|
||||||
resolve: (val: any) => void
|
resolve: (val: any) => void
|
||||||
@ -546,6 +551,8 @@ export class EngineConnection {
|
|||||||
export type EngineCommand = Models['WebSocketRequest_type']
|
export type EngineCommand = Models['WebSocketRequest_type']
|
||||||
type ModelTypes = Models['OkModelingCmdResponse_type']['type']
|
type ModelTypes = Models['OkModelingCmdResponse_type']['type']
|
||||||
|
|
||||||
|
type CommandTypes = Models['ModelingCmd_type']['type']
|
||||||
|
|
||||||
type UnreliableResponses = Extract<
|
type UnreliableResponses = Extract<
|
||||||
Models['OkModelingCmdResponse_type'],
|
Models['OkModelingCmdResponse_type'],
|
||||||
{ type: 'highlight_set_entity' }
|
{ type: 'highlight_set_entity' }
|
||||||
@ -687,15 +694,22 @@ export class EngineCommandManager {
|
|||||||
const resolve = command.resolve
|
const resolve = command.resolve
|
||||||
this.artifactMap[id] = {
|
this.artifactMap[id] = {
|
||||||
type: 'result',
|
type: 'result',
|
||||||
|
range: command.range,
|
||||||
|
commandType: command.commandType,
|
||||||
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
}
|
}
|
||||||
resolve({
|
resolve({
|
||||||
id,
|
id,
|
||||||
|
commandType: command.commandType,
|
||||||
|
range: command.range,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.artifactMap[id] = {
|
this.artifactMap[id] = {
|
||||||
type: 'result',
|
type: 'result',
|
||||||
|
commandType: command?.commandType,
|
||||||
|
range: command?.range,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -747,8 +761,29 @@ export class EngineCommandManager {
|
|||||||
delete this.unreliableSubscriptions[event][id]
|
delete this.unreliableSubscriptions[event][id]
|
||||||
}
|
}
|
||||||
endSession() {
|
endSession() {
|
||||||
// this.websocket?.close()
|
// TODO: instead of sending a single command with `object_ids: Object.keys(this.artifactMap)`
|
||||||
// socket.off('command')
|
// we need to loop over them each individualy because if the engine doesn't recognise a single
|
||||||
|
// id the whole command fails.
|
||||||
|
Object.entries(this.artifactMap).forEach(([id, artifact]) => {
|
||||||
|
const artifactTypesToDelete: ArtifactMap[string]['commandType'][] = [
|
||||||
|
// 'start_path' creates a new scene object for the path, which is why it needs to be deleted,
|
||||||
|
// however all of the segments in the path are its children so there don't need to be deleted.
|
||||||
|
// this fact is very opaque in the api and docs (as to what should can be deleted).
|
||||||
|
// Using an array is the list is likely to grow.
|
||||||
|
'start_path',
|
||||||
|
]
|
||||||
|
if (!artifactTypesToDelete.includes(artifact.commandType)) return
|
||||||
|
|
||||||
|
const deletCmd: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'remove_scene_objects',
|
||||||
|
object_ids: [id],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.engineConnection?.send(deletCmd)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
cusorsSelected(selections: {
|
cusorsSelected(selections: {
|
||||||
otherSelections: Selections['otherSelections']
|
otherSelections: Selections['otherSelections']
|
||||||
@ -801,11 +836,20 @@ export class EngineCommandManager {
|
|||||||
JSON.stringify(command)
|
JSON.stringify(command)
|
||||||
)
|
)
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
} else if (
|
||||||
|
cmd.type === 'mouse_move' &&
|
||||||
|
this.engineConnection.unreliableDataChannel
|
||||||
|
) {
|
||||||
|
cmd.sequence = this.outSequence
|
||||||
|
this.outSequence++
|
||||||
|
this.engineConnection?.unreliableDataChannel?.send(
|
||||||
|
JSON.stringify(command)
|
||||||
|
)
|
||||||
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
console.log('sending command', command)
|
|
||||||
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
||||||
this.engineConnection?.send(command)
|
this.engineConnection?.send(command)
|
||||||
return this.handlePendingCommand(command.cmd_id)
|
return this.handlePendingCommand(command.cmd_id, command.cmd)
|
||||||
}
|
}
|
||||||
sendModelingCommand({
|
sendModelingCommand({
|
||||||
id,
|
id,
|
||||||
@ -823,15 +867,35 @@ export class EngineCommandManager {
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
this.engineConnection?.send(command)
|
this.engineConnection?.send(command)
|
||||||
return this.handlePendingCommand(id)
|
if (typeof command !== 'string' && command.type === 'modeling_cmd_req') {
|
||||||
|
return this.handlePendingCommand(id, command?.cmd, range)
|
||||||
|
} else if (typeof command === 'string') {
|
||||||
|
const parseCommand: EngineCommand = JSON.parse(command)
|
||||||
|
if (parseCommand.type === 'modeling_cmd_req')
|
||||||
|
return this.handlePendingCommand(id, parseCommand?.cmd, range)
|
||||||
}
|
}
|
||||||
handlePendingCommand(id: string) {
|
throw 'shouldnt reach here'
|
||||||
|
}
|
||||||
|
handlePendingCommand(
|
||||||
|
id: string,
|
||||||
|
command: Models['ModelingCmd_type'],
|
||||||
|
range?: SourceRange
|
||||||
|
) {
|
||||||
let resolve: (val: any) => void = () => {}
|
let resolve: (val: any) => void = () => {}
|
||||||
const promise = new Promise((_resolve, reject) => {
|
const promise = new Promise((_resolve, reject) => {
|
||||||
resolve = _resolve
|
resolve = _resolve
|
||||||
})
|
})
|
||||||
|
const getParentId = (): string | undefined => {
|
||||||
|
if (command.type === 'extend_path') {
|
||||||
|
return command.path
|
||||||
|
}
|
||||||
|
// TODO handle other commands that have a parent
|
||||||
|
}
|
||||||
this.artifactMap[id] = {
|
this.artifactMap[id] = {
|
||||||
|
range: range || [0, 0],
|
||||||
type: 'pending',
|
type: 'pending',
|
||||||
|
commandType: command.type,
|
||||||
|
parentId: getParentId(),
|
||||||
promise,
|
promise,
|
||||||
resolve,
|
resolve,
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,27 @@ export function throttle<T>(
|
|||||||
return throttled
|
return throttled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
let timeout: ReturnType<typeof setTimeout> | null
|
||||||
|
let latestArgs: T
|
||||||
|
|
||||||
|
function later() {
|
||||||
|
timeout = null
|
||||||
|
func(latestArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
function deffered(args: T) {
|
||||||
|
latestArgs = args
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
timeout = setTimeout(later, wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
return deffered
|
||||||
|
}
|
||||||
|
|
||||||
export function getNormalisedCoordinates({
|
export function getNormalisedCoordinates({
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
EngineCommandManager,
|
EngineCommandManager,
|
||||||
} from './lang/std/engineConnection'
|
} from './lang/std/engineConnection'
|
||||||
import { KCLError } from './lang/errors'
|
import { KCLError } from './lang/errors'
|
||||||
|
import { defferExecution } from 'lib/utils'
|
||||||
|
|
||||||
export type Selection = {
|
export type Selection = {
|
||||||
type: 'default' | 'line-end' | 'line-mid'
|
type: 'default' | 'line-end' | 'line-mid'
|
||||||
@ -132,7 +133,9 @@ export interface StoreState {
|
|||||||
) => void
|
) => void
|
||||||
updateAstAsync: (ast: Program, focusPath?: PathToNode) => void
|
updateAstAsync: (ast: Program, focusPath?: PathToNode) => void
|
||||||
code: string
|
code: string
|
||||||
|
defferedCode: string
|
||||||
setCode: (code: string) => void
|
setCode: (code: string) => void
|
||||||
|
defferedSetCode: (code: string) => void
|
||||||
formatCode: () => void
|
formatCode: () => void
|
||||||
errorState: {
|
errorState: {
|
||||||
isError: boolean
|
isError: boolean
|
||||||
@ -168,6 +171,8 @@ export interface StoreState {
|
|||||||
streamWidth: number
|
streamWidth: number
|
||||||
streamHeight: number
|
streamHeight: number
|
||||||
}) => void
|
}) => void
|
||||||
|
isExecuting: boolean
|
||||||
|
setIsExecuting: (isExecuting: boolean) => void
|
||||||
|
|
||||||
showHomeMenu: boolean
|
showHomeMenu: boolean
|
||||||
setHomeShowMenu: (showMenu: boolean) => void
|
setHomeShowMenu: (showMenu: boolean) => void
|
||||||
@ -186,7 +191,12 @@ let pendingAstUpdates: number[] = []
|
|||||||
|
|
||||||
export const useStore = create<StoreState>()(
|
export const useStore = create<StoreState>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => {
|
||||||
|
const setDefferedCode = defferExecution(
|
||||||
|
(code: string) => set({ defferedCode: code }),
|
||||||
|
600
|
||||||
|
)
|
||||||
|
return {
|
||||||
editorView: null,
|
editorView: null,
|
||||||
setEditorView: (editorView) => {
|
setEditorView: (editorView) => {
|
||||||
set({ editorView })
|
set({ editorView })
|
||||||
@ -282,7 +292,10 @@ export const useStore = create<StoreState>()(
|
|||||||
|
|
||||||
set({ ast: astWithUpdatedSource, code: newCode })
|
set({ ast: astWithUpdatedSource, code: newCode })
|
||||||
if (focusPath) {
|
if (focusPath) {
|
||||||
const { node } = getNodeFromPath<any>(astWithUpdatedSource, focusPath)
|
const { node } = getNodeFromPath<any>(
|
||||||
|
astWithUpdatedSource,
|
||||||
|
focusPath
|
||||||
|
)
|
||||||
const { start, end } = node
|
const { start, end } = node
|
||||||
if (!start || !end) return
|
if (!start || !end) return
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -310,8 +323,11 @@ export const useStore = create<StoreState>()(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
code: '',
|
code: '',
|
||||||
setCode: (code) => {
|
defferedCode: '',
|
||||||
|
setCode: (code) => set({ code, defferedCode: code }),
|
||||||
|
defferedSetCode: (code) => {
|
||||||
set({ code })
|
set({ code })
|
||||||
|
setDefferedCode(code)
|
||||||
},
|
},
|
||||||
formatCode: async () => {
|
formatCode: async () => {
|
||||||
const code = get().code
|
const code = get().code
|
||||||
@ -353,6 +369,8 @@ export const useStore = create<StoreState>()(
|
|||||||
setFileId: (fileId) => set({ fileId }),
|
setFileId: (fileId) => set({ fileId }),
|
||||||
streamDimensions: { streamWidth: 1280, streamHeight: 720 },
|
streamDimensions: { streamWidth: 1280, streamHeight: 720 },
|
||||||
setStreamDimensions: (streamDimensions) => set({ streamDimensions }),
|
setStreamDimensions: (streamDimensions) => set({ streamDimensions }),
|
||||||
|
isExecuting: false,
|
||||||
|
setIsExecuting: (isExecuting) => set({ isExecuting }),
|
||||||
|
|
||||||
// tauri specific app settings
|
// tauri specific app settings
|
||||||
defaultDir: {
|
defaultDir: {
|
||||||
@ -366,7 +384,8 @@ export const useStore = create<StoreState>()(
|
|||||||
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
|
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
|
||||||
homeMenuItems: [],
|
homeMenuItems: [],
|
||||||
setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }),
|
setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }),
|
||||||
}),
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'store',
|
name: 'store',
|
||||||
partialize: (state) =>
|
partialize: (state) =>
|
||||||
|
Reference in New Issue
Block a user