Compare commits
3 Commits
v0.29.0
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
99c62bf0f0 | |||
ee20d4781f | |||
1e80738b2a |
@ -1,5 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { EngineCommandManagerEvents } from 'lang/std/engineConnection'
|
|
||||||
import { engineCommandManager, sceneInfra } from 'lib/singletons'
|
import { engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||||
import { throttle, isReducedMotion } from 'lib/utils'
|
import { throttle, isReducedMotion } from 'lib/utils'
|
||||||
|
|
||||||
@ -14,12 +13,9 @@ export const CamToggle = () => {
|
|||||||
const [enableRotate, setEnableRotate] = useState(true)
|
const [enableRotate, setEnableRotate] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
engineCommandManager.addEventListener(
|
engineCommandManager.waitForReady.then(async () => {
|
||||||
EngineCommandManagerEvents.SceneReady,
|
|
||||||
async () => {
|
|
||||||
sceneInfra.camControls.dollyZoom(fov)
|
sceneInfra.camControls.dollyZoom(fov)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const toggleCamera = () => {
|
const toggleCamera = () => {
|
||||||
|
@ -176,7 +176,6 @@ const FileTreeItem = ({
|
|||||||
)
|
)
|
||||||
codeManager.writeToFile()
|
codeManager.writeToFile()
|
||||||
|
|
||||||
// Prevent seeing the model built one piece at a time when changing files
|
|
||||||
kclManager.isFirstRender = true
|
kclManager.isFirstRender = true
|
||||||
kclManager.executeCode(true).then(() => {
|
kclManager.executeCode(true).then(() => {
|
||||||
kclManager.isFirstRender = false
|
kclManager.isFirstRender = false
|
||||||
|
@ -47,7 +47,7 @@ const Loading = ({ children }: React.PropsWithChildren) => {
|
|||||||
onConnectionStateChange as EventListener
|
onConnectionStateChange as EventListener
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [engineCommandManager, engineCommandManager.engineConnection])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Don't set long loading time if there's a more severe error
|
// Don't set long loading time if there's a more severe error
|
||||||
|
@ -78,12 +78,7 @@ import { err, trap } from 'lib/trap'
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { modelingMachineEvent } from 'editor/manager'
|
import { modelingMachineEvent } from 'editor/manager'
|
||||||
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
|
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
|
||||||
import {
|
import { ExportIntent } from 'lang/std/engineConnection'
|
||||||
ExportIntent,
|
|
||||||
EngineConnectionState,
|
|
||||||
EngineConnectionStateType,
|
|
||||||
EngineConnectionEvents,
|
|
||||||
} from 'lang/std/engineConnection'
|
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -159,10 +154,7 @@ export const ModelingMachineProvider = ({
|
|||||||
sceneInfra.camControls.syncDirection = 'engineToClient'
|
sceneInfra.camControls.syncDirection = 'engineToClient'
|
||||||
|
|
||||||
store.videoElement?.pause()
|
store.videoElement?.pause()
|
||||||
|
|
||||||
kclManager.isFirstRender = true
|
|
||||||
kclManager.executeCode().then(() => {
|
kclManager.executeCode().then(() => {
|
||||||
kclManager.isFirstRender = false
|
|
||||||
if (engineCommandManager.engineConnection?.idleMode) return
|
if (engineCommandManager.engineConnection?.idleMode) return
|
||||||
|
|
||||||
store.videoElement?.play().catch((e) => {
|
store.videoElement?.play().catch((e) => {
|
||||||
@ -917,19 +909,15 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
useSetupEngineManager(
|
useSetupEngineManager(streamRef, token, {
|
||||||
streamRef,
|
|
||||||
modelingSend,
|
|
||||||
modelingState.context,
|
|
||||||
{
|
|
||||||
pool: pool,
|
pool: pool,
|
||||||
theme: theme.current,
|
theme: theme.current,
|
||||||
highlightEdges: highlightEdges.current,
|
highlightEdges: highlightEdges.current,
|
||||||
enableSSAO: enableSSAO.current,
|
enableSSAO: enableSSAO.current,
|
||||||
|
modelingSend,
|
||||||
|
modelingContext: modelingState.context,
|
||||||
showScaleGrid: showScaleGrid.current,
|
showScaleGrid: showScaleGrid.current,
|
||||||
},
|
})
|
||||||
token
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
kclManager.registerExecuteCallback(() => {
|
kclManager.registerExecuteCallback(() => {
|
||||||
@ -957,25 +945,17 @@ export const ModelingMachineProvider = ({
|
|||||||
}, [modelingState.context.selectionRanges])
|
}, [modelingState.context.selectionRanges])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onConnectionStateChanged = ({ detail }: CustomEvent) => {
|
const offlineCallback = () => {
|
||||||
// If we are in sketch mode we need to exit it.
|
// If we are in sketch mode we need to exit it.
|
||||||
// TODO: how do i check if we are in a sketch mode, I only want to call
|
// TODO: how do i check if we are in a sketch mode, I only want to call
|
||||||
// this then.
|
// this then.
|
||||||
if (detail.type === EngineConnectionStateType.Disconnecting) {
|
|
||||||
modelingSend({ type: 'Cancel' })
|
modelingSend({ type: 'Cancel' })
|
||||||
}
|
}
|
||||||
}
|
window.addEventListener('offline', offlineCallback)
|
||||||
engineCommandManager.engineConnection?.addEventListener(
|
|
||||||
EngineConnectionEvents.ConnectionStateChanged,
|
|
||||||
onConnectionStateChanged as EventListener
|
|
||||||
)
|
|
||||||
return () => {
|
return () => {
|
||||||
engineCommandManager.engineConnection?.removeEventListener(
|
window.removeEventListener('offline', offlineCallback)
|
||||||
EngineConnectionEvents.ConnectionStateChanged,
|
|
||||||
onConnectionStateChanged as EventListener
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}, [engineCommandManager.engineConnection, modelingSend])
|
}, [modelingSend])
|
||||||
|
|
||||||
// Allow using the delete key to delete solids
|
// Allow using the delete key to delete solids
|
||||||
useHotkeys(['backspace', 'delete', 'del'], () => {
|
useHotkeys(['backspace', 'delete', 'del'], () => {
|
||||||
|
@ -64,6 +64,13 @@ export const KclEditorPane = () => {
|
|||||||
: context.app.theme.current
|
: context.app.theme.current
|
||||||
const { copilotLSP, kclLSP } = useLspContext()
|
const { copilotLSP, kclLSP } = useLspContext()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') return
|
||||||
|
const onlineCallback = () => kclManager.executeCode(true)
|
||||||
|
window.addEventListener('online', onlineCallback)
|
||||||
|
return () => window.removeEventListener('online', onlineCallback)
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Since these already exist in the editor, we don't need to define them
|
// Since these already exist in the editor, we don't need to define them
|
||||||
// with the wrapper.
|
// with the wrapper.
|
||||||
useHotkeys('mod+z', (e) => {
|
useHotkeys('mod+z', (e) => {
|
||||||
|
@ -191,7 +191,6 @@ export const SettingsAuthProviderBase = ({
|
|||||||
allSettingsIncludesUnitChange ||
|
allSettingsIncludesUnitChange ||
|
||||||
resetSettingsIncludesUnitChange
|
resetSettingsIncludesUnitChange
|
||||||
) {
|
) {
|
||||||
// Unit changes requires a re-exec of code
|
|
||||||
kclManager.isFirstRender = true
|
kclManager.isFirstRender = true
|
||||||
kclManager.executeCode(true).then(() => {
|
kclManager.executeCode(true).then(() => {
|
||||||
kclManager.isFirstRender = false
|
kclManager.isFirstRender = false
|
||||||
|
@ -11,27 +11,21 @@ import { sendSelectEventToEngine } from 'lib/selections'
|
|||||||
import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons'
|
import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||||
import { useAppStream } from 'AppState'
|
import { useAppStream } from 'AppState'
|
||||||
import {
|
import {
|
||||||
EngineCommandManagerEvents,
|
|
||||||
EngineConnectionStateType,
|
EngineConnectionStateType,
|
||||||
DisconnectingType,
|
DisconnectingType,
|
||||||
} from 'lang/std/engineConnection'
|
} from 'lang/std/engineConnection'
|
||||||
|
|
||||||
enum StreamState {
|
|
||||||
Playing = 'playing',
|
|
||||||
Paused = 'paused',
|
|
||||||
Resuming = 'resuming',
|
|
||||||
Unset = 'unset',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Stream = () => {
|
export const Stream = () => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
const [isFirstRender, setIsFirstRender] = useState(kclManager.isFirstRender)
|
||||||
const [clickCoords, setClickCoords] = useState<{ x: number; y: number }>()
|
const [clickCoords, setClickCoords] = useState<{ x: number; y: number }>()
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const { state, send, context } = useModelingContext()
|
const { state, send, context } = useModelingContext()
|
||||||
const { mediaStream } = useAppStream()
|
const { mediaStream } = useAppStream()
|
||||||
const { overallState, immediateState } = useNetworkContext()
|
const { overallState, immediateState } = useNetworkContext()
|
||||||
const [streamState, setStreamState] = useState(StreamState.Unset)
|
const [isFreezeFrame, setIsFreezeFrame] = useState(false)
|
||||||
|
const [isPaused, setIsPaused] = useState(false)
|
||||||
|
|
||||||
const IDLE = settings.context.app.streamIdleMode.current
|
const IDLE = settings.context.app.streamIdleMode.current
|
||||||
|
|
||||||
@ -44,7 +38,10 @@ export const Stream = () => {
|
|||||||
immediateState.type === EngineConnectionStateType.Disconnecting &&
|
immediateState.type === EngineConnectionStateType.Disconnecting &&
|
||||||
immediateState.value.type === DisconnectingType.Pause
|
immediateState.value.type === DisconnectingType.Pause
|
||||||
) {
|
) {
|
||||||
setStreamState(StreamState.Paused)
|
setIsPaused(true)
|
||||||
|
}
|
||||||
|
if (immediateState.type === EngineConnectionStateType.Connecting) {
|
||||||
|
setIsPaused(false)
|
||||||
}
|
}
|
||||||
}, [immediateState])
|
}, [immediateState])
|
||||||
|
|
||||||
@ -79,11 +76,8 @@ export const Stream = () => {
|
|||||||
let timeoutIdIdleA: ReturnType<typeof setTimeout> | undefined = undefined
|
let timeoutIdIdleA: ReturnType<typeof setTimeout> | undefined = undefined
|
||||||
|
|
||||||
const teardown = () => {
|
const teardown = () => {
|
||||||
// Already paused
|
|
||||||
if (streamState === StreamState.Paused) return
|
|
||||||
|
|
||||||
videoRef.current?.pause()
|
videoRef.current?.pause()
|
||||||
setStreamState(StreamState.Paused)
|
setIsFreezeFrame(true)
|
||||||
sceneInfra.modelingSend({ type: 'Cancel' })
|
sceneInfra.modelingSend({ type: 'Cancel' })
|
||||||
// Give video time to pause
|
// Give video time to pause
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
@ -97,7 +91,7 @@ export const Stream = () => {
|
|||||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
||||||
} else if (!engineCommandManager.engineConnection?.isReady()) {
|
} else if (!engineCommandManager.engineConnection?.isReady()) {
|
||||||
clearTimeout(timeoutIdIdleA)
|
clearTimeout(timeoutIdIdleA)
|
||||||
setStreamState(StreamState.Resuming)
|
engineCommandManager.engineConnection?.connect(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,16 +106,11 @@ export const Stream = () => {
|
|||||||
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
|
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
|
||||||
|
|
||||||
const onAnyInput = () => {
|
const onAnyInput = () => {
|
||||||
if (streamState === StreamState.Playing) {
|
|
||||||
// Clear both timers
|
// Clear both timers
|
||||||
clearTimeout(timeoutIdIdleA)
|
clearTimeout(timeoutIdIdleA)
|
||||||
clearTimeout(timeoutIdIdleB)
|
clearTimeout(timeoutIdIdleB)
|
||||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||||
}
|
}
|
||||||
if (streamState === StreamState.Paused) {
|
|
||||||
setStreamState(StreamState.Resuming)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IDLE) {
|
if (IDLE) {
|
||||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
||||||
@ -135,27 +124,7 @@ export const Stream = () => {
|
|||||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSceneReady = () => {
|
|
||||||
kclManager.isFirstRender = true
|
|
||||||
setStreamState(StreamState.Playing)
|
|
||||||
kclManager.executeCode(true).then(() => {
|
|
||||||
videoRef.current?.play().catch((e) => {
|
|
||||||
console.warn('Video playing was prevented', e, videoRef.current)
|
|
||||||
})
|
|
||||||
kclManager.isFirstRender = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
engineCommandManager.addEventListener(
|
|
||||||
EngineCommandManagerEvents.SceneReady,
|
|
||||||
onSceneReady
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
engineCommandManager.removeEventListener(
|
|
||||||
EngineCommandManagerEvents.SceneReady,
|
|
||||||
onSceneReady
|
|
||||||
)
|
|
||||||
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
||||||
capture: true,
|
capture: true,
|
||||||
})
|
})
|
||||||
@ -183,11 +152,10 @@ export const Stream = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [IDLE, streamState])
|
}, [IDLE])
|
||||||
|
|
||||||
// HOT FIX: for https://github.com/KittyCAD/modeling-app/pull/3250
|
|
||||||
// TODO review if there's a better way to play the stream again.
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setIsFirstRender(kclManager.isFirstRender)
|
||||||
if (!kclManager.isFirstRender)
|
if (!kclManager.isFirstRender)
|
||||||
setTimeout(() =>
|
setTimeout(() =>
|
||||||
// execute in the next event loop
|
// execute in the next event loop
|
||||||
@ -195,6 +163,7 @@ export const Stream = () => {
|
|||||||
console.warn('Video playing was prevented', e, videoRef.current)
|
console.warn('Video playing was prevented', e, videoRef.current)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
setIsFreezeFrame(!kclManager.isFirstRender)
|
||||||
}, [kclManager.isFirstRender])
|
}, [kclManager.isFirstRender])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -319,8 +288,7 @@ export const Stream = () => {
|
|||||||
<ClientSideScene
|
<ClientSideScene
|
||||||
cameraControls={settings.context.modeling.mouseControls.current}
|
cameraControls={settings.context.modeling.mouseControls.current}
|
||||||
/>
|
/>
|
||||||
{(streamState === StreamState.Paused ||
|
{isPaused && (
|
||||||
streamState === StreamState.Resuming) && (
|
|
||||||
<div className="text-center absolute inset-0">
|
<div className="text-center absolute inset-0">
|
||||||
<div
|
<div
|
||||||
className="flex flex-col items-center justify-center h-screen"
|
className="flex flex-col items-center justify-center h-screen"
|
||||||
@ -342,19 +310,16 @@ export const Stream = () => {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-base mt-2 text-primary bold">
|
<p className="text-base mt-2 text-primary bold">Paused</p>
|
||||||
{streamState === StreamState.Paused && 'Paused'}
|
|
||||||
{streamState === StreamState.Resuming && 'Resuming'}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(!isNetworkOkay || isLoading || kclManager.isFirstRender) && (
|
{(!isNetworkOkay || isLoading || isFirstRender) && !isFreezeFrame && (
|
||||||
<div className="text-center absolute inset-0">
|
<div className="text-center absolute inset-0">
|
||||||
<Loading>
|
<Loading>
|
||||||
{!isNetworkOkay && !isLoading && !kclManager.isFirstRender ? (
|
{!isNetworkOkay && !isLoading ? (
|
||||||
<span data-testid="loading-stream">Stream disconnected...</span>
|
<span data-testid="loading-stream">Stream disconnected...</span>
|
||||||
) : !isLoading && kclManager.isFirstRender ? (
|
) : !isLoading && isFirstRender ? (
|
||||||
<span data-testid="loading-stream">Building scene...</span>
|
<span data-testid="loading-stream">Building scene...</span>
|
||||||
) : (
|
) : (
|
||||||
<span data-testid="loading-stream">Loading stream...</span>
|
<span data-testid="loading-stream">Loading stream...</span>
|
||||||
|
@ -4,30 +4,29 @@ import { deferExecution } from 'lib/utils'
|
|||||||
import { Themes } from 'lib/theme'
|
import { Themes } from 'lib/theme'
|
||||||
import { makeDefaultPlanes, modifyGrid } from 'lang/wasm'
|
import { makeDefaultPlanes, modifyGrid } from 'lang/wasm'
|
||||||
import { useModelingContext } from './useModelingContext'
|
import { useModelingContext } from './useModelingContext'
|
||||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
|
||||||
import { useAppState, useAppStream } from 'AppState'
|
import { useAppState, useAppStream } from 'AppState'
|
||||||
import { SettingsViaQueryString } from 'lib/settings/settingsTypes'
|
|
||||||
import {
|
|
||||||
EngineConnectionStateType,
|
|
||||||
EngineConnectionEvents,
|
|
||||||
DisconnectingType,
|
|
||||||
} from 'lang/std/engineConnection'
|
|
||||||
|
|
||||||
export function useSetupEngineManager(
|
export function useSetupEngineManager(
|
||||||
streamRef: React.RefObject<HTMLDivElement>,
|
streamRef: React.RefObject<HTMLDivElement>,
|
||||||
modelingSend: ReturnType<typeof useModelingContext>['send'],
|
token?: string,
|
||||||
modelingContext: ReturnType<typeof useModelingContext>['context'],
|
|
||||||
settings = {
|
settings = {
|
||||||
pool: null,
|
pool: null,
|
||||||
theme: Themes.System,
|
theme: Themes.System,
|
||||||
highlightEdges: true,
|
highlightEdges: true,
|
||||||
enableSSAO: true,
|
enableSSAO: true,
|
||||||
|
modelingSend: (() => {}) as any,
|
||||||
|
modelingContext: {} as any,
|
||||||
showScaleGrid: false,
|
showScaleGrid: false,
|
||||||
} as SettingsViaQueryString,
|
} as {
|
||||||
token?: string
|
pool: string | null
|
||||||
|
theme: Themes
|
||||||
|
highlightEdges: boolean
|
||||||
|
enableSSAO: boolean
|
||||||
|
modelingSend: ReturnType<typeof useModelingContext>['send']
|
||||||
|
modelingContext: ReturnType<typeof useModelingContext>['context']
|
||||||
|
showScaleGrid: boolean
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
const networkContext = useNetworkContext()
|
|
||||||
const { pingPongHealth, immediateState } = networkContext
|
|
||||||
const { setAppState } = useAppState()
|
const { setAppState } = useAppState()
|
||||||
const { setMediaStream } = useAppStream()
|
const { setMediaStream } = useAppStream()
|
||||||
|
|
||||||
@ -36,10 +35,10 @@ export function useSetupEngineManager(
|
|||||||
if (settings.pool) {
|
if (settings.pool) {
|
||||||
// override the pool param (?pool=) to request a specific engine instance
|
// override the pool param (?pool=) to request a specific engine instance
|
||||||
// from a particular pool.
|
// from a particular pool.
|
||||||
engineCommandManager.settings.pool = settings.pool
|
engineCommandManager.pool = settings.pool
|
||||||
}
|
}
|
||||||
|
|
||||||
const startEngineInstance = () => {
|
const startEngineInstance = (restart: boolean = false) => {
|
||||||
// Load the engine command manager once with the initial width and height,
|
// Load the engine command manager once with the initial width and height,
|
||||||
// then we do not want to reload it.
|
// then we do not want to reload it.
|
||||||
const { width: quadWidth, height: quadHeight } = getDimensions(
|
const { width: quadWidth, height: quadHeight } = getDimensions(
|
||||||
@ -51,6 +50,14 @@ export function useSetupEngineManager(
|
|||||||
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
|
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
|
||||||
width: quadWidth,
|
width: quadWidth,
|
||||||
height: quadHeight,
|
height: quadHeight,
|
||||||
|
executeCode: () => {
|
||||||
|
// We only want to execute the code here that we already have set.
|
||||||
|
// Nothing else.
|
||||||
|
kclManager.isFirstRender = true
|
||||||
|
return kclManager.executeCode(true).then(() => {
|
||||||
|
kclManager.isFirstRender = false
|
||||||
|
})
|
||||||
|
},
|
||||||
token,
|
token,
|
||||||
settings,
|
settings,
|
||||||
makeDefaultPlanes: () => {
|
makeDefaultPlanes: () => {
|
||||||
@ -60,7 +67,7 @@ export function useSetupEngineManager(
|
|||||||
return modifyGrid(kclManager.engineCommandManager, hidden)
|
return modifyGrid(kclManager.engineCommandManager, hidden)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
modelingSend({
|
settings.modelingSend({
|
||||||
type: 'Set context',
|
type: 'Set context',
|
||||||
data: {
|
data: {
|
||||||
streamDimensions: {
|
streamDimensions: {
|
||||||
@ -83,27 +90,9 @@ export function useSetupEngineManager(
|
|||||||
}, [
|
}, [
|
||||||
streamRef?.current?.offsetWidth,
|
streamRef?.current?.offsetWidth,
|
||||||
streamRef?.current?.offsetHeight,
|
streamRef?.current?.offsetHeight,
|
||||||
modelingSend,
|
settings.modelingSend,
|
||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (pingPongHealth === 'TIMEOUT') {
|
|
||||||
engineCommandManager.tearDown()
|
|
||||||
}
|
|
||||||
}, [pingPongHealth])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const intervalId = setInterval(() => {
|
|
||||||
if (immediateState.type === EngineConnectionStateType.Disconnected) {
|
|
||||||
engineCommandManager.engineConnection = undefined
|
|
||||||
startEngineInstance()
|
|
||||||
}
|
|
||||||
}, 3000)
|
|
||||||
return () => {
|
|
||||||
clearInterval(intervalId)
|
|
||||||
}
|
|
||||||
}, [immediateState])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = deferExecution(() => {
|
const handleResize = deferExecution(() => {
|
||||||
const { width, height } = getDimensions(
|
const { width, height } = getDimensions(
|
||||||
@ -111,14 +100,14 @@ export function useSetupEngineManager(
|
|||||||
streamRef?.current?.offsetHeight ?? 0
|
streamRef?.current?.offsetHeight ?? 0
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
modelingContext.store.streamDimensions.streamWidth !== width ||
|
settings.modelingContext.store.streamDimensions.streamWidth !== width ||
|
||||||
modelingContext.store.streamDimensions.streamHeight !== height
|
settings.modelingContext.store.streamDimensions.streamHeight !== height
|
||||||
) {
|
) {
|
||||||
engineCommandManager.handleResize({
|
engineCommandManager.handleResize({
|
||||||
streamWidth: width,
|
streamWidth: width,
|
||||||
streamHeight: height,
|
streamHeight: height,
|
||||||
})
|
})
|
||||||
modelingSend({
|
settings.modelingSend({
|
||||||
type: 'Set context',
|
type: 'Set context',
|
||||||
data: {
|
data: {
|
||||||
streamDimensions: {
|
streamDimensions: {
|
||||||
@ -131,7 +120,7 @@ export function useSetupEngineManager(
|
|||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
const onOnline = () => {
|
const onOnline = () => {
|
||||||
startEngineInstance()
|
startEngineInstance(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onVisibilityChange = () => {
|
const onVisibilityChange = () => {
|
||||||
@ -147,18 +136,10 @@ export function useSetupEngineManager(
|
|||||||
window.document.addEventListener('visibilitychange', onVisibilityChange)
|
window.document.addEventListener('visibilitychange', onVisibilityChange)
|
||||||
|
|
||||||
const onAnyInput = () => {
|
const onAnyInput = () => {
|
||||||
const isEngineNotReadyOrConnecting =
|
if (
|
||||||
!engineCommandManager.engineConnection?.isReady() &&
|
!engineCommandManager.engineConnection?.isReady() &&
|
||||||
!engineCommandManager.engineConnection?.isConnecting()
|
!engineCommandManager.engineConnection?.isConnecting()
|
||||||
|
) {
|
||||||
const conn = engineCommandManager.engineConnection
|
|
||||||
|
|
||||||
const isStreamPaused =
|
|
||||||
conn?.state.type === EngineConnectionStateType.Disconnecting &&
|
|
||||||
conn?.state.value.type === DisconnectingType.Pause
|
|
||||||
|
|
||||||
if (isEngineNotReadyOrConnecting || isStreamPaused) {
|
|
||||||
engineCommandManager.engineConnection = undefined
|
|
||||||
startEngineInstance()
|
startEngineInstance()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,6 +150,7 @@ export function useSetupEngineManager(
|
|||||||
window.document.addEventListener('touchstart', onAnyInput)
|
window.document.addEventListener('touchstart', onAnyInput)
|
||||||
|
|
||||||
const onOffline = () => {
|
const onOffline = () => {
|
||||||
|
kclManager.isFirstRender = true
|
||||||
engineCommandManager.tearDown()
|
engineCommandManager.tearDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,6 +211,7 @@ export class KclManager {
|
|||||||
type: string
|
type: string
|
||||||
}
|
}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
await this?.engineCommandManager?.waitForReady
|
||||||
const currentExecutionId = executionId || Date.now()
|
const currentExecutionId = executionId || Date.now()
|
||||||
this._cancelTokens.set(currentExecutionId, false)
|
this._cancelTokens.set(currentExecutionId, false)
|
||||||
|
|
||||||
@ -300,6 +301,7 @@ export class KclManager {
|
|||||||
codeManager.updateCodeEditor(newCode)
|
codeManager.updateCodeEditor(newCode)
|
||||||
// Write the file to disk.
|
// Write the file to disk.
|
||||||
await codeManager.writeToFile()
|
await codeManager.writeToFile()
|
||||||
|
await this?.engineCommandManager?.waitForReady
|
||||||
this._ast = { ...newAst }
|
this._ast = { ...newAst }
|
||||||
|
|
||||||
const { logs, errors, programMemory } = await executeAst({
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
|
@ -14,10 +14,6 @@ import {
|
|||||||
} from './artifactGraph'
|
} from './artifactGraph'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||||
import {
|
|
||||||
EngineCommandManagerEvents,
|
|
||||||
EngineConnectionEvents,
|
|
||||||
} from 'lang/std/engineConnection'
|
|
||||||
import { CI, VITE_KC_DEV_TOKEN } from 'env'
|
import { CI, VITE_KC_DEV_TOKEN } from 'env'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
@ -117,18 +113,20 @@ beforeAll(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
|
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
|
||||||
await new Promise((resolve) => {
|
|
||||||
engineCommandManager.start({
|
engineCommandManager.start({
|
||||||
// disableWebRTC: true,
|
disableWebRTC: true,
|
||||||
token: VITE_KC_DEV_TOKEN,
|
token: VITE_KC_DEV_TOKEN,
|
||||||
// there does seem to be a minimum resolution, not sure what it is but 256 works ok.
|
// there does seem to be a minimum resolution, not sure what it is but 256 works ok.
|
||||||
width: 256,
|
width: 256,
|
||||||
height: 256,
|
height: 256,
|
||||||
|
executeCode: () => {},
|
||||||
makeDefaultPlanes: () => makeDefaultPlanes(engineCommandManager),
|
makeDefaultPlanes: () => makeDefaultPlanes(engineCommandManager),
|
||||||
setMediaStream: () => {},
|
setMediaStream: () => {},
|
||||||
setIsStreamReady: () => {},
|
setIsStreamReady: () => {},
|
||||||
modifyGrid: async () => {},
|
modifyGrid: async () => {},
|
||||||
callbackOnEngineLiteConnect: async () => {
|
})
|
||||||
|
await engineCommandManager.waitForReady
|
||||||
|
|
||||||
const cacheEntries = Object.entries(codeToWriteCacheFor) as [
|
const cacheEntries = Object.entries(codeToWriteCacheFor) as [
|
||||||
CodeKey,
|
CodeKey,
|
||||||
string
|
string
|
||||||
@ -138,9 +136,9 @@ beforeAll(async () => {
|
|||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
if (err(ast)) {
|
if (err(ast)) {
|
||||||
console.error(ast)
|
console.error(ast)
|
||||||
return Promise.reject(ast)
|
throw ast
|
||||||
}
|
}
|
||||||
const result = await kclManager.executeAst(ast)
|
await kclManager.executeAst(ast)
|
||||||
|
|
||||||
cacheToWriteToFileTemp[codeKey] = {
|
cacheToWriteToFileTemp[codeKey] = {
|
||||||
orderedCommands: engineCommandManager.orderedCommands,
|
orderedCommands: engineCommandManager.orderedCommands,
|
||||||
@ -151,10 +149,6 @@ beforeAll(async () => {
|
|||||||
|
|
||||||
await fsp.mkdir(pathStart, { recursive: true })
|
await fsp.mkdir(pathStart, { recursive: true })
|
||||||
await fsp.writeFile(fullPath, cache)
|
await fsp.writeFile(fullPath, cache)
|
||||||
resolve(true)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}, 20_000)
|
}, 20_000)
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Program, SourceRange } from 'lang/wasm'
|
import { Program, SourceRange } from 'lang/wasm'
|
||||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
import { deferExecution, isOverlap, uuidv4 } from 'lib/utils'
|
import { deferExecution, isOverlap, uuidv4 } from 'lib/utils'
|
||||||
@ -15,10 +15,9 @@ import {
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { exportMake } from 'lib/exportMake'
|
import { exportMake } from 'lib/exportMake'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { SettingsViaQueryString } from 'lib/settings/settingsTypes'
|
|
||||||
|
|
||||||
// TODO(paultag): This ought to be tweakable.
|
// TODO(paultag): This ought to be tweakable.
|
||||||
const pingIntervalMs = 1000
|
const pingIntervalMs = 10000
|
||||||
|
|
||||||
function isHighlightSetEntity_type(
|
function isHighlightSetEntity_type(
|
||||||
data: any
|
data: any
|
||||||
@ -229,7 +228,6 @@ class EngineConnection extends EventTarget {
|
|||||||
unreliableDataChannel?: RTCDataChannel
|
unreliableDataChannel?: RTCDataChannel
|
||||||
mediaStream?: MediaStream
|
mediaStream?: MediaStream
|
||||||
idleMode: boolean = false
|
idleMode: boolean = false
|
||||||
promise?: Promise<void>
|
|
||||||
|
|
||||||
onIceCandidate = function (
|
onIceCandidate = function (
|
||||||
this: RTCPeerConnection,
|
this: RTCPeerConnection,
|
||||||
@ -297,33 +295,24 @@ class EngineConnection extends EventTarget {
|
|||||||
private engineCommandManager: EngineCommandManager
|
private engineCommandManager: EngineCommandManager
|
||||||
|
|
||||||
private pingPongSpan: { ping?: Date; pong?: Date }
|
private pingPongSpan: { ping?: Date; pong?: Date }
|
||||||
private pingIntervalId: ReturnType<typeof setInterval> = setInterval(() => {},
|
private pingIntervalId: ReturnType<typeof setInterval>
|
||||||
60_000)
|
|
||||||
isUsingConnectionLite: boolean = false
|
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
url,
|
url,
|
||||||
token,
|
token,
|
||||||
callbackOnEngineLiteConnect,
|
|
||||||
}: {
|
}: {
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
url: string
|
url: string
|
||||||
token?: string
|
token?: string
|
||||||
callbackOnEngineLiteConnect?: () => void
|
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.engineCommandManager = engineCommandManager
|
this.engineCommandManager = engineCommandManager
|
||||||
this.url = url
|
this.url = url
|
||||||
this.token = token
|
this.token = token
|
||||||
this.pingPongSpan = { ping: undefined, pong: undefined }
|
|
||||||
|
|
||||||
if (callbackOnEngineLiteConnect) {
|
this.pingPongSpan = { ping: undefined, pong: undefined }
|
||||||
this.connectLite(callbackOnEngineLiteConnect)
|
|
||||||
this.isUsingConnectionLite = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Without an interval ping, our connection will timeout.
|
// Without an interval ping, our connection will timeout.
|
||||||
// If this.idleMode is true we skip this logic so only reconnect
|
// If this.idleMode is true we skip this logic so only reconnect
|
||||||
@ -333,22 +322,13 @@ class EngineConnection extends EventTarget {
|
|||||||
|
|
||||||
switch (this.state.type as EngineConnectionStateType) {
|
switch (this.state.type as EngineConnectionStateType) {
|
||||||
case EngineConnectionStateType.ConnectionEstablished:
|
case EngineConnectionStateType.ConnectionEstablished:
|
||||||
// If there was no reply to the last ping, report a timeout and
|
// If there was no reply to the last ping, report a timeout.
|
||||||
// teardown the connection.
|
|
||||||
if (this.pingPongSpan.ping && !this.pingPongSpan.pong) {
|
if (this.pingPongSpan.ping && !this.pingPongSpan.pong) {
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
|
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
|
||||||
detail: 'TIMEOUT',
|
detail: 'TIMEOUT',
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
this.state = {
|
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
|
||||||
value: {
|
|
||||||
type: DisconnectingType.Timeout,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
this.disconnectAll()
|
|
||||||
|
|
||||||
// Otherwise check the time between was >= pingIntervalMs,
|
// Otherwise check the time between was >= pingIntervalMs,
|
||||||
// and if it was, then it's bad network health.
|
// and if it was, then it's bad network health.
|
||||||
} else if (this.pingPongSpan.ping && this.pingPongSpan.pong) {
|
} else if (this.pingPongSpan.ping && this.pingPongSpan.pong) {
|
||||||
@ -378,15 +358,13 @@ class EngineConnection extends EventTarget {
|
|||||||
break
|
break
|
||||||
case EngineConnectionStateType.Disconnecting:
|
case EngineConnectionStateType.Disconnecting:
|
||||||
case EngineConnectionStateType.Disconnected:
|
case EngineConnectionStateType.Disconnected:
|
||||||
// We will do reconnection elsewhere, because we basically need
|
// Reconnect if we have disconnected.
|
||||||
// to destroy this EngineConnection, and this setInterval loop
|
if (!this.isConnecting()) this.connect(true)
|
||||||
// lives inside it. (lee) I might change this in the future so it's
|
|
||||||
// outside this class.
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (this.isConnecting()) break
|
if (this.isConnecting()) break
|
||||||
// Means we never could do an initial connection. Reconnect everything.
|
// Means we never could do an initial connection. Reconnect everything.
|
||||||
if (!this.pingPongSpan.ping) this.connect()
|
if (!this.pingPongSpan.ping) this.connect(true)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}, pingIntervalMs)
|
}, pingIntervalMs)
|
||||||
@ -394,101 +372,6 @@ class EngineConnection extends EventTarget {
|
|||||||
this.connect()
|
this.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SHOULD ONLY BE USED FOR VITESTS
|
|
||||||
connectLite(callback: () => void) {
|
|
||||||
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${256}&video_res_height=${256}`
|
|
||||||
|
|
||||||
this.websocket = new WebSocket(url, [])
|
|
||||||
this.websocket.binaryType = 'arraybuffer'
|
|
||||||
|
|
||||||
this.send = (a) => {
|
|
||||||
if (!this.websocket) return
|
|
||||||
this.websocket.send(JSON.stringify(a))
|
|
||||||
}
|
|
||||||
this.onWebSocketOpen = (event) => {
|
|
||||||
this.send({
|
|
||||||
type: 'headers',
|
|
||||||
headers: { Authorization: `Bearer ${VITE_KC_DEV_TOKEN}` },
|
|
||||||
})
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
this.tearDown = () => {}
|
|
||||||
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
|
||||||
|
|
||||||
this.websocket?.addEventListener('message', ((event: MessageEvent) => {
|
|
||||||
const message: Models['WebSocketResponse_type'] = JSON.parse(event.data)
|
|
||||||
const pending =
|
|
||||||
this.engineCommandManager.pendingCommands[message.request_id || '']
|
|
||||||
if (!('resp' in message)) return
|
|
||||||
|
|
||||||
let resp = message.resp
|
|
||||||
|
|
||||||
// If there's no body to the response, we can bail here.
|
|
||||||
if (!resp || !resp.type) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (resp.type) {
|
|
||||||
case 'pong':
|
|
||||||
break
|
|
||||||
|
|
||||||
// Only fires on successful authentication.
|
|
||||||
case 'ice_server_info':
|
|
||||||
callback()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(
|
|
||||||
pending &&
|
|
||||||
message.success &&
|
|
||||||
(message.resp.type === 'modeling' ||
|
|
||||||
message.resp.type === 'modeling_batch')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (
|
|
||||||
message.resp.type === 'modeling' &&
|
|
||||||
pending.command.type === 'modeling_cmd_req' &&
|
|
||||||
message.request_id
|
|
||||||
) {
|
|
||||||
this.engineCommandManager.responseMap[message.request_id] = message.resp
|
|
||||||
} else if (
|
|
||||||
message.resp.type === 'modeling_batch' &&
|
|
||||||
pending.command.type === 'modeling_cmd_batch_req'
|
|
||||||
) {
|
|
||||||
let individualPendingResponses: {
|
|
||||||
[key: string]: Models['WebSocketRequest_type']
|
|
||||||
} = {}
|
|
||||||
pending.command.requests.forEach(({ cmd, cmd_id }) => {
|
|
||||||
individualPendingResponses[cmd_id] = {
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd,
|
|
||||||
cmd_id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Object.entries(message.resp.data.responses).forEach(
|
|
||||||
([commandId, response]) => {
|
|
||||||
if (!('response' in response)) return
|
|
||||||
const command = individualPendingResponses[commandId]
|
|
||||||
if (!command) return
|
|
||||||
if (command.type === 'modeling_cmd_req')
|
|
||||||
this.engineCommandManager.responseMap[commandId] = {
|
|
||||||
type: 'modeling',
|
|
||||||
data: {
|
|
||||||
modeling_response: response.response,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pending.resolve([message])
|
|
||||||
delete this.engineCommandManager.pendingCommands[message.request_id || '']
|
|
||||||
}) as EventListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
isConnecting() {
|
isConnecting() {
|
||||||
return this.state.type === EngineConnectionStateType.Connecting
|
return this.state.type === EngineConnectionStateType.Connecting
|
||||||
}
|
}
|
||||||
@ -499,29 +382,58 @@ class EngineConnection extends EventTarget {
|
|||||||
|
|
||||||
tearDown(opts?: { idleMode: boolean }) {
|
tearDown(opts?: { idleMode: boolean }) {
|
||||||
this.idleMode = opts?.idleMode ?? false
|
this.idleMode = opts?.idleMode ?? false
|
||||||
|
this.disconnectAll()
|
||||||
clearInterval(this.pingIntervalId)
|
clearInterval(this.pingIntervalId)
|
||||||
|
|
||||||
if (opts?.idleMode) {
|
this.pc?.removeEventListener('icecandidate', this.onIceCandidate)
|
||||||
this.state = {
|
this.pc?.removeEventListener('icecandidateerror', this.onIceCandidateError)
|
||||||
|
this.pc?.removeEventListener(
|
||||||
|
'connectionstatechange',
|
||||||
|
this.onConnectionStateChange
|
||||||
|
)
|
||||||
|
this.pc?.removeEventListener('track', this.onTrack)
|
||||||
|
|
||||||
|
this.unreliableDataChannel?.removeEventListener(
|
||||||
|
'open',
|
||||||
|
this.onDataChannelOpen
|
||||||
|
)
|
||||||
|
this.unreliableDataChannel?.removeEventListener(
|
||||||
|
'close',
|
||||||
|
this.onDataChannelClose
|
||||||
|
)
|
||||||
|
this.unreliableDataChannel?.removeEventListener(
|
||||||
|
'error',
|
||||||
|
this.onDataChannelError
|
||||||
|
)
|
||||||
|
this.unreliableDataChannel?.removeEventListener(
|
||||||
|
'message',
|
||||||
|
this.onDataChannelMessage
|
||||||
|
)
|
||||||
|
this.pc?.removeEventListener('datachannel', this.onDataChannel)
|
||||||
|
|
||||||
|
this.websocket?.removeEventListener('open', this.onWebSocketOpen)
|
||||||
|
this.websocket?.removeEventListener('close', this.onWebSocketClose)
|
||||||
|
this.websocket?.removeEventListener('error', this.onWebSocketError)
|
||||||
|
this.websocket?.removeEventListener('message', this.onWebSocketMessage)
|
||||||
|
|
||||||
|
window.removeEventListener(
|
||||||
|
'use-network-status-ready',
|
||||||
|
this.onNetworkStatusReady
|
||||||
|
)
|
||||||
|
|
||||||
|
this.state = opts?.idleMode
|
||||||
|
? {
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
value: {
|
value: {
|
||||||
type: DisconnectingType.Pause,
|
type: DisconnectingType.Pause,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
: {
|
||||||
// Pass the state along
|
|
||||||
if (this.state.type === EngineConnectionStateType.Disconnecting) return
|
|
||||||
if (this.state.type === EngineConnectionStateType.Disconnected) return
|
|
||||||
|
|
||||||
// Otherwise it's by default a "quit"
|
|
||||||
this.state = {
|
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
value: {
|
value: {
|
||||||
type: DisconnectingType.Quit,
|
type: DisconnectingType.Quit,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disconnectAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -531,16 +443,17 @@ class EngineConnection extends EventTarget {
|
|||||||
* This will attempt the full handshake, and retry if the connection
|
* This will attempt the full handshake, and retry if the connection
|
||||||
* did not establish.
|
* did not establish.
|
||||||
*/
|
*/
|
||||||
connect(reconnecting?: boolean): Promise<void> {
|
connect(reconnecting?: boolean) {
|
||||||
return new Promise((resolve) => {
|
|
||||||
if (this.isConnecting() || this.isReady()) {
|
if (this.isConnecting() || this.isReady()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const createPeerConnection = () => {
|
const createPeerConnection = () => {
|
||||||
|
if (!this.engineCommandManager.disableWebRTC) {
|
||||||
this.pc = new RTCPeerConnection({
|
this.pc = new RTCPeerConnection({
|
||||||
bundlePolicy: 'max-bundle',
|
bundlePolicy: 'max-bundle',
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Other parts of the application expect pc to be initialized when firing.
|
// Other parts of the application expect pc to be initialized when firing.
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
@ -593,10 +506,7 @@ class EngineConnection extends EventTarget {
|
|||||||
`ICE candidate returned an error: ${event.errorCode}: ${event.errorText} for ${event.url}`
|
`ICE candidate returned an error: ${event.errorCode}: ${event.errorText} for ${event.url}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
this.pc?.addEventListener?.(
|
this.pc?.addEventListener?.('icecandidateerror', this.onIceCandidateError)
|
||||||
'icecandidateerror',
|
|
||||||
this.onIceCandidateError
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionstatechange_event
|
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionstatechange_event
|
||||||
// Event type: generic Event type...
|
// Event type: generic Event type...
|
||||||
@ -613,19 +523,8 @@ class EngineConnection extends EventTarget {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case 'disconnected':
|
|
||||||
case 'failed':
|
case 'failed':
|
||||||
this.pc?.removeEventListener('icecandidate', this.onIceCandidate)
|
this.disconnectAll()
|
||||||
this.pc?.removeEventListener(
|
|
||||||
'icecandidateerror',
|
|
||||||
this.onIceCandidateError
|
|
||||||
)
|
|
||||||
this.pc?.removeEventListener(
|
|
||||||
'connectionstatechange',
|
|
||||||
this.onConnectionStateChange
|
|
||||||
)
|
|
||||||
this.pc?.removeEventListener('track', this.onTrack)
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
value: {
|
value: {
|
||||||
@ -636,7 +535,6 @@ class EngineConnection extends EventTarget {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
this.disconnectAll()
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -701,8 +599,7 @@ class EngineConnection extends EventTarget {
|
|||||||
videoTrackReport.framesPerSecond || 0
|
videoTrackReport.framesPerSecond || 0
|
||||||
client_metrics.rtc_freeze_count =
|
client_metrics.rtc_freeze_count =
|
||||||
videoTrackReport.freezeCount || 0
|
videoTrackReport.freezeCount || 0
|
||||||
client_metrics.rtc_jitter_sec =
|
client_metrics.rtc_jitter_sec = videoTrackReport.jitter || 0.0
|
||||||
videoTrackReport.jitter || 0.0
|
|
||||||
client_metrics.rtc_keyframes_decoded =
|
client_metrics.rtc_keyframes_decoded =
|
||||||
videoTrackReport.keyFramesDecoded || 0
|
videoTrackReport.keyFramesDecoded || 0
|
||||||
client_metrics.rtc_total_freezes_duration_sec =
|
client_metrics.rtc_total_freezes_duration_sec =
|
||||||
@ -713,8 +610,7 @@ class EngineConnection extends EventTarget {
|
|||||||
videoTrackReport.frameWidth || 0
|
videoTrackReport.frameWidth || 0
|
||||||
client_metrics.rtc_packets_lost =
|
client_metrics.rtc_packets_lost =
|
||||||
videoTrackReport.packetsLost || 0
|
videoTrackReport.packetsLost || 0
|
||||||
client_metrics.rtc_pli_count =
|
client_metrics.rtc_pli_count = videoTrackReport.pliCount || 0
|
||||||
videoTrackReport.pliCount || 0
|
|
||||||
} else if (videoTrackReport.type === 'transport') {
|
} else if (videoTrackReport.type === 'transport') {
|
||||||
// videoTrackReport.bytesReceived,
|
// videoTrackReport.bytesReceived,
|
||||||
// videoTrackReport.bytesSent,
|
// videoTrackReport.bytesSent,
|
||||||
@ -756,9 +652,7 @@ class EngineConnection extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Everything is now connected.
|
// Everything is now connected.
|
||||||
this.state = {
|
this.state = { type: EngineConnectionStateType.ConnectionEstablished }
|
||||||
type: EngineConnectionStateType.ConnectionEstablished,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.engineCommandManager.inSequence = 1
|
this.engineCommandManager.inSequence = 1
|
||||||
|
|
||||||
@ -772,32 +666,17 @@ class EngineConnection extends EventTarget {
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.onDataChannelClose = (event) => {
|
this.onDataChannelClose = (event) => {
|
||||||
this.unreliableDataChannel?.removeEventListener(
|
|
||||||
'open',
|
|
||||||
this.onDataChannelOpen
|
|
||||||
)
|
|
||||||
this.unreliableDataChannel?.removeEventListener(
|
|
||||||
'close',
|
|
||||||
this.onDataChannelClose
|
|
||||||
)
|
|
||||||
this.unreliableDataChannel?.removeEventListener(
|
|
||||||
'error',
|
|
||||||
this.onDataChannelError
|
|
||||||
)
|
|
||||||
this.unreliableDataChannel?.removeEventListener(
|
|
||||||
'message',
|
|
||||||
this.onDataChannelMessage
|
|
||||||
)
|
|
||||||
this.pc?.removeEventListener('datachannel', this.onDataChannel)
|
|
||||||
this.disconnectAll()
|
this.disconnectAll()
|
||||||
|
this.finalizeIfAllConnectionsClosed()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.unreliableDataChannel?.addEventListener(
|
this.unreliableDataChannel?.addEventListener(
|
||||||
'close',
|
'close',
|
||||||
this.onDataChannelClose
|
this.onDataChannelClose
|
||||||
)
|
)
|
||||||
|
|
||||||
this.onDataChannelError = (event) => {
|
this.onDataChannelError = (event) => {
|
||||||
|
this.disconnectAll()
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
value: {
|
value: {
|
||||||
@ -808,7 +687,6 @@ class EngineConnection extends EventTarget {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
this.disconnectAll()
|
|
||||||
}
|
}
|
||||||
this.unreliableDataChannel?.addEventListener(
|
this.unreliableDataChannel?.addEventListener(
|
||||||
'error',
|
'error',
|
||||||
@ -818,8 +696,7 @@ class EngineConnection extends EventTarget {
|
|||||||
this.onDataChannelMessage = (event) => {
|
this.onDataChannelMessage = (event) => {
|
||||||
const result: UnreliableResponses = JSON.parse(event.data)
|
const result: UnreliableResponses = JSON.parse(event.data)
|
||||||
Object.values(
|
Object.values(
|
||||||
this.engineCommandManager.unreliableSubscriptions[result.type] ||
|
this.engineCommandManager.unreliableSubscriptions[result.type] || {}
|
||||||
{}
|
|
||||||
).forEach(
|
).forEach(
|
||||||
// TODO: There is only one response that uses the unreliable channel atm,
|
// TODO: There is only one response that uses the unreliable channel atm,
|
||||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||||
@ -879,28 +756,23 @@ class EngineConnection extends EventTarget {
|
|||||||
// Send an initial ping
|
// Send an initial ping
|
||||||
this.send({ type: 'ping' })
|
this.send({ type: 'ping' })
|
||||||
this.pingPongSpan.ping = new Date()
|
this.pingPongSpan.ping = new Date()
|
||||||
|
if (this.engineCommandManager.disableWebRTC) {
|
||||||
|
this.engineCommandManager
|
||||||
|
.initPlanes()
|
||||||
|
.then(() => this.engineCommandManager.resolveReady())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
||||||
|
|
||||||
this.onWebSocketClose = (event) => {
|
this.onWebSocketClose = (event) => {
|
||||||
this.websocket?.removeEventListener('open', this.onWebSocketOpen)
|
|
||||||
this.websocket?.removeEventListener('close', this.onWebSocketClose)
|
|
||||||
this.websocket?.removeEventListener('error', this.onWebSocketError)
|
|
||||||
this.websocket?.removeEventListener(
|
|
||||||
'message',
|
|
||||||
this.onWebSocketMessage
|
|
||||||
)
|
|
||||||
|
|
||||||
window.removeEventListener(
|
|
||||||
'use-network-status-ready',
|
|
||||||
this.onNetworkStatusReady
|
|
||||||
)
|
|
||||||
|
|
||||||
this.disconnectAll()
|
this.disconnectAll()
|
||||||
|
this.finalizeIfAllConnectionsClosed()
|
||||||
}
|
}
|
||||||
this.websocket.addEventListener('close', this.onWebSocketClose)
|
this.websocket.addEventListener('close', this.onWebSocketClose)
|
||||||
|
|
||||||
this.onWebSocketError = (event) => {
|
this.onWebSocketError = (event) => {
|
||||||
|
this.disconnectAll()
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
value: {
|
value: {
|
||||||
@ -911,8 +783,6 @@ class EngineConnection extends EventTarget {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disconnectAll()
|
|
||||||
}
|
}
|
||||||
this.websocket.addEventListener('error', this.onWebSocketError)
|
this.websocket.addEventListener('error', this.onWebSocketError)
|
||||||
|
|
||||||
@ -928,9 +798,7 @@ class EngineConnection extends EventTarget {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
const message: Models['WebSocketResponse_type'] = JSON.parse(event.data)
|
||||||
event.data
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!message.success) {
|
if (!message.success) {
|
||||||
const errorsString = message?.errors
|
const errorsString = message?.errors
|
||||||
@ -986,8 +854,6 @@ class EngineConnection extends EventTarget {
|
|||||||
case 'pong':
|
case 'pong':
|
||||||
this.pingPongSpan.pong = new Date()
|
this.pingPongSpan.pong = new Date()
|
||||||
break
|
break
|
||||||
|
|
||||||
// Only fires on successful authentication.
|
|
||||||
case 'ice_server_info':
|
case 'ice_server_info':
|
||||||
let ice_servers = resp.data?.ice_servers
|
let ice_servers = resp.data?.ice_servers
|
||||||
|
|
||||||
@ -1067,6 +933,7 @@ class EngineConnection extends EventTarget {
|
|||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
// The local description is invalid, so there's no point continuing.
|
// The local description is invalid, so there's no point continuing.
|
||||||
|
this.disconnectAll()
|
||||||
this.state = {
|
this.state = {
|
||||||
type: EngineConnectionStateType.Disconnecting,
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
value: {
|
value: {
|
||||||
@ -1077,7 +944,6 @@ class EngineConnection extends EventTarget {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
this.disconnectAll()
|
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -1148,7 +1014,6 @@ class EngineConnection extends EventTarget {
|
|||||||
this.onNetworkStatusReady
|
this.onNetworkStatusReady
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
// Do not change this back to an object or any, we should only be sending the
|
// Do not change this back to an object or any, we should only be sending the
|
||||||
// WebSocketRequest type!
|
// WebSocketRequest type!
|
||||||
@ -1162,9 +1027,6 @@ class EngineConnection extends EventTarget {
|
|||||||
// Do not change this back to an object or any, we should only be sending the
|
// Do not change this back to an object or any, we should only be sending the
|
||||||
// WebSocketRequest type!
|
// WebSocketRequest type!
|
||||||
send(message: Models['WebSocketRequest_type']) {
|
send(message: Models['WebSocketRequest_type']) {
|
||||||
// Not connected, don't send anything
|
|
||||||
if (this.websocket?.readyState === 3) return
|
|
||||||
|
|
||||||
// TODO(paultag): Add in logic to determine the connection state and
|
// TODO(paultag): Add in logic to determine the connection state and
|
||||||
// take actions if needed?
|
// take actions if needed?
|
||||||
this.websocket?.send(
|
this.websocket?.send(
|
||||||
@ -1172,35 +1034,18 @@ class EngineConnection extends EventTarget {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
disconnectAll() {
|
disconnectAll() {
|
||||||
if (this.websocket?.readyState === 1) {
|
|
||||||
this.websocket?.close()
|
this.websocket?.close()
|
||||||
}
|
|
||||||
if (this.unreliableDataChannel?.readyState === 'open') {
|
|
||||||
this.unreliableDataChannel?.close()
|
this.unreliableDataChannel?.close()
|
||||||
}
|
|
||||||
if (this.pc?.connectionState === 'connected') {
|
|
||||||
this.pc?.close()
|
this.pc?.close()
|
||||||
}
|
|
||||||
|
|
||||||
this.webrtcStatsCollector = undefined
|
this.webrtcStatsCollector = undefined
|
||||||
|
}
|
||||||
// Already triggered
|
finalizeIfAllConnectionsClosed() {
|
||||||
if (this.state.type === EngineConnectionStateType.Disconnected) return
|
const allClosed =
|
||||||
|
this.websocket?.readyState === 3 &&
|
||||||
const closedPc = !this.pc || this.pc?.connectionState === 'closed'
|
this.pc?.connectionState === 'closed' &&
|
||||||
const closedUDC =
|
|
||||||
!this.unreliableDataChannel ||
|
|
||||||
this.unreliableDataChannel?.readyState === 'closed'
|
this.unreliableDataChannel?.readyState === 'closed'
|
||||||
|
if (allClosed) {
|
||||||
// Do not check when timing out because websockets take forever to
|
|
||||||
// report their disconnected state.
|
|
||||||
const closedWS =
|
|
||||||
(this.state.type === EngineConnectionStateType.Disconnecting &&
|
|
||||||
this.state.value.type === DisconnectingType.Timeout) ||
|
|
||||||
!this.websocket ||
|
|
||||||
this.websocket?.readyState === 3
|
|
||||||
|
|
||||||
if (closedPc && closedUDC && closedWS) {
|
|
||||||
// Do not notify the rest of the program that we have cut off anything.
|
// Do not notify the rest of the program that we have cut off anything.
|
||||||
this.state = { type: EngineConnectionStateType.Disconnected }
|
this.state = { type: EngineConnectionStateType.Disconnected }
|
||||||
}
|
}
|
||||||
@ -1248,11 +1093,7 @@ export type CommandLog =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum EngineCommandManagerEvents {
|
export enum EngineCommandManagerEvents {
|
||||||
// engineConnection is available but scene setup may not have run
|
|
||||||
EngineAvailable = 'engine-available',
|
EngineAvailable = 'engine-available',
|
||||||
|
|
||||||
// the whole scene is ready (settings loaded)
|
|
||||||
SceneReady = 'scene-ready',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1310,6 +1151,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
* any out-of-order late responses in the unreliable channel.
|
* any out-of-order late responses in the unreliable channel.
|
||||||
*/
|
*/
|
||||||
inSequence = 1
|
inSequence = 1
|
||||||
|
pool?: string
|
||||||
engineConnection?: EngineConnection
|
engineConnection?: EngineConnection
|
||||||
defaultPlanes: DefaultPlanes | null = null
|
defaultPlanes: DefaultPlanes | null = null
|
||||||
commandLogs: CommandLog[] = []
|
commandLogs: CommandLog[] = []
|
||||||
@ -1318,8 +1160,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
reject: (reason: any) => void
|
reject: (reason: any) => void
|
||||||
commandId: string
|
commandId: string
|
||||||
}
|
}
|
||||||
settings: SettingsViaQueryString
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export intent traxcks the intent of the export. If it is null there is no
|
* Export intent traxcks the intent of the export. If it is null there is no
|
||||||
* export in progress. Otherwise it is an enum value of the intent.
|
* export in progress. Otherwise it is an enum value of the intent.
|
||||||
@ -1327,6 +1167,15 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
*/
|
*/
|
||||||
private _exportIntent: ExportIntent | null = null
|
private _exportIntent: ExportIntent | null = null
|
||||||
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
||||||
|
resolveReady = () => {}
|
||||||
|
/** Folks should realize that wait for ready does not get called _everytime_
|
||||||
|
* the connection resets and restarts, it only gets called the first time.
|
||||||
|
*
|
||||||
|
* Be careful what you put here.
|
||||||
|
*/
|
||||||
|
waitForReady: Promise<void> = new Promise((resolve) => {
|
||||||
|
this.resolveReady = resolve
|
||||||
|
})
|
||||||
|
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
[event: string]: {
|
[event: string]: {
|
||||||
@ -1339,19 +1188,11 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
} = {} as any
|
} = {} as any
|
||||||
|
|
||||||
constructor(settings?: SettingsViaQueryString) {
|
constructor(pool?: string) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.engineConnection = undefined
|
this.engineConnection = undefined
|
||||||
this.settings = settings
|
this.pool = pool
|
||||||
? settings
|
|
||||||
: {
|
|
||||||
pool: null,
|
|
||||||
theme: Themes.Dark,
|
|
||||||
highlightEdges: true,
|
|
||||||
enableSSAO: true,
|
|
||||||
showScaleGrid: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _camControlsCameraChange = () => {}
|
private _camControlsCameraChange = () => {}
|
||||||
@ -1373,6 +1214,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
private onEngineConnectionNewTrack = ({
|
private onEngineConnectionNewTrack = ({
|
||||||
detail,
|
detail,
|
||||||
}: CustomEvent<NewTrackArgs>) => {}
|
}: CustomEvent<NewTrackArgs>) => {}
|
||||||
|
disableWebRTC = false
|
||||||
modelingSend: ReturnType<typeof useModelingContext>['send'] =
|
modelingSend: ReturnType<typeof useModelingContext>['send'] =
|
||||||
(() => {}) as any
|
(() => {}) as any
|
||||||
|
|
||||||
@ -1385,38 +1227,40 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start({
|
start({
|
||||||
|
disableWebRTC = false,
|
||||||
setMediaStream,
|
setMediaStream,
|
||||||
setIsStreamReady,
|
setIsStreamReady,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
executeCode,
|
||||||
token,
|
token,
|
||||||
makeDefaultPlanes,
|
makeDefaultPlanes,
|
||||||
modifyGrid,
|
modifyGrid,
|
||||||
settings = {
|
settings = {
|
||||||
pool: null,
|
|
||||||
theme: Themes.Dark,
|
theme: Themes.Dark,
|
||||||
highlightEdges: true,
|
highlightEdges: true,
|
||||||
enableSSAO: true,
|
enableSSAO: true,
|
||||||
showScaleGrid: false,
|
showScaleGrid: false,
|
||||||
},
|
},
|
||||||
// When passed, use a completely separate connecting code path that simply
|
|
||||||
// opens a websocket and this is a function that is called when connected.
|
|
||||||
callbackOnEngineLiteConnect,
|
|
||||||
}: {
|
}: {
|
||||||
callbackOnEngineLiteConnect?: () => void
|
disableWebRTC?: boolean
|
||||||
setMediaStream: (stream: MediaStream) => void
|
setMediaStream: (stream: MediaStream) => void
|
||||||
setIsStreamReady: (isStreamReady: boolean) => void
|
setIsStreamReady: (isStreamReady: boolean) => void
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
|
executeCode: () => void
|
||||||
token?: string
|
token?: string
|
||||||
makeDefaultPlanes: () => Promise<DefaultPlanes>
|
makeDefaultPlanes: () => Promise<DefaultPlanes>
|
||||||
modifyGrid: (hidden: boolean) => Promise<void>
|
modifyGrid: (hidden: boolean) => Promise<void>
|
||||||
settings?: SettingsViaQueryString
|
settings?: {
|
||||||
}) {
|
theme: Themes
|
||||||
if (settings) {
|
highlightEdges: boolean
|
||||||
this.settings = settings
|
enableSSAO: boolean
|
||||||
|
showScaleGrid: boolean
|
||||||
}
|
}
|
||||||
|
}) {
|
||||||
this.makeDefaultPlanes = makeDefaultPlanes
|
this.makeDefaultPlanes = makeDefaultPlanes
|
||||||
|
this.disableWebRTC = disableWebRTC
|
||||||
this.modifyGrid = modifyGrid
|
this.modifyGrid = modifyGrid
|
||||||
if (width === 0 || height === 0) {
|
if (width === 0 || height === 0) {
|
||||||
return
|
return
|
||||||
@ -1431,28 +1275,22 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalSettings = this.settings.enableSSAO
|
const additionalSettings = settings.enableSSAO ? '&post_effect=ssao' : ''
|
||||||
? '&post_effect=ssao'
|
const pool = this.pool === undefined ? '' : `&pool=${this.pool}`
|
||||||
: ''
|
|
||||||
const pool = !this.settings.pool ? '' : `&pool=${this.settings.pool}`
|
|
||||||
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}${additionalSettings}${pool}`
|
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}${additionalSettings}${pool}`
|
||||||
this.engineConnection = new EngineConnection({
|
this.engineConnection = new EngineConnection({
|
||||||
engineCommandManager: this,
|
engineCommandManager: this,
|
||||||
url,
|
url,
|
||||||
token,
|
token,
|
||||||
callbackOnEngineLiteConnect,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Nothing more to do when using a lite engine initialization
|
|
||||||
if (callbackOnEngineLiteConnect) return
|
|
||||||
|
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent(EngineCommandManagerEvents.EngineAvailable, {
|
new CustomEvent(EngineCommandManagerEvents.EngineAvailable, {
|
||||||
detail: this.engineConnection,
|
detail: this.engineConnection,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
this.onEngineConnectionOpened = async () => {
|
this.onEngineConnectionOpened = () => {
|
||||||
// Set the stream background color
|
// Set the stream background color
|
||||||
// This takes RGBA values from 0-1
|
// This takes RGBA values from 0-1
|
||||||
// So we convert from the conventional 0-255 found in Figma
|
// So we convert from the conventional 0-255 found in Figma
|
||||||
@ -1462,12 +1300,12 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'set_background_color',
|
type: 'set_background_color',
|
||||||
color: getThemeColorForEngine(this.settings.theme),
|
color: getThemeColorForEngine(settings.theme),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Sets the default line colors
|
// Sets the default line colors
|
||||||
const opposingTheme = getOppositeTheme(this.settings.theme)
|
const opposingTheme = getOppositeTheme(settings.theme)
|
||||||
this.sendSceneCommand({
|
this.sendSceneCommand({
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
@ -1483,7 +1321,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'edge_lines_visible' as any, // TODO: update kittycad.ts to use the correct type
|
type: 'edge_lines_visible' as any, // TODO: update kittycad.ts to use the correct type
|
||||||
hidden: !this.settings.highlightEdges,
|
hidden: !settings.highlightEdges,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1500,19 +1338,13 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
// We want modify the grid first because we don't want it to flash.
|
// We want modify the grid first because we don't want it to flash.
|
||||||
// Ideally these would already be default hidden in engine (TODO do
|
// Ideally these would already be default hidden in engine (TODO do
|
||||||
// that) https://github.com/KittyCAD/engine/issues/2282
|
// that) https://github.com/KittyCAD/engine/issues/2282
|
||||||
this.modifyGrid(!this.settings.showScaleGrid)?.then(async () => {
|
this.modifyGrid(!settings.showScaleGrid)?.then(async () => {
|
||||||
await this.initPlanes()
|
await this.initPlanes()
|
||||||
|
this.resolveReady()
|
||||||
setIsStreamReady(true)
|
setIsStreamReady(true)
|
||||||
|
await executeCode()
|
||||||
// Other parts of the application should use this to react on scene ready.
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent(EngineCommandManagerEvents.SceneReady, {
|
|
||||||
detail: this.engineConnection,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.engineConnection.addEventListener(
|
this.engineConnection.addEventListener(
|
||||||
EngineConnectionEvents.Opened,
|
EngineConnectionEvents.Opened,
|
||||||
this.onEngineConnectionOpened
|
this.onEngineConnectionOpened
|
||||||
@ -1709,8 +1541,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
EngineConnectionEvents.ConnectionStarted,
|
EngineConnectionEvents.ConnectionStarted,
|
||||||
this.onEngineConnectionStarted
|
this.onEngineConnectionStarted
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResize({
|
handleResize({
|
||||||
@ -1739,28 +1569,25 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
|
|
||||||
tearDown(opts?: { idleMode: boolean }) {
|
tearDown(opts?: { idleMode: boolean }) {
|
||||||
if (this.engineConnection) {
|
if (this.engineConnection) {
|
||||||
for (const pending of Object.values(this.pendingCommands)) {
|
this.engineConnection.removeEventListener(
|
||||||
pending.reject('no connection to send on')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.engineConnection?.removeEventListener?.(
|
|
||||||
EngineConnectionEvents.Opened,
|
EngineConnectionEvents.Opened,
|
||||||
this.onEngineConnectionOpened
|
this.onEngineConnectionOpened
|
||||||
)
|
)
|
||||||
this.engineConnection.removeEventListener?.(
|
this.engineConnection.removeEventListener(
|
||||||
EngineConnectionEvents.Closed,
|
EngineConnectionEvents.Closed,
|
||||||
this.onEngineConnectionClosed
|
this.onEngineConnectionClosed
|
||||||
)
|
)
|
||||||
this.engineConnection.removeEventListener?.(
|
this.engineConnection.removeEventListener(
|
||||||
EngineConnectionEvents.ConnectionStarted,
|
EngineConnectionEvents.ConnectionStarted,
|
||||||
this.onEngineConnectionStarted
|
this.onEngineConnectionStarted
|
||||||
)
|
)
|
||||||
this.engineConnection.removeEventListener?.(
|
this.engineConnection.removeEventListener(
|
||||||
EngineConnectionEvents.NewTrack,
|
EngineConnectionEvents.NewTrack,
|
||||||
this.onEngineConnectionNewTrack as EventListener
|
this.onEngineConnectionNewTrack as EventListener
|
||||||
)
|
)
|
||||||
|
|
||||||
this.engineConnection?.tearDown(opts)
|
this.engineConnection?.tearDown(opts)
|
||||||
|
this.engineConnection = undefined
|
||||||
|
|
||||||
// Our window.tearDown assignment causes this case to happen which is
|
// Our window.tearDown assignment causes this case to happen which is
|
||||||
// only really for tests.
|
// only really for tests.
|
||||||
@ -1942,17 +1769,17 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
commandStr: string,
|
commandStr: string,
|
||||||
idToRangeStr: string
|
idToRangeStr: string
|
||||||
): Promise<string | void> {
|
): Promise<string | void> {
|
||||||
if (this.engineConnection === undefined) return Promise.resolve()
|
if (this.engineConnection === undefined) {
|
||||||
if (
|
return Promise.resolve()
|
||||||
!this.engineConnection?.isReady() &&
|
}
|
||||||
!this.engineConnection.isUsingConnectionLite
|
if (!this.engineConnection?.isReady() && !this.disableWebRTC)
|
||||||
)
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
if (id === undefined) return Promise.reject(new Error('id is undefined'))
|
if (id === undefined) return Promise.reject(new Error('id is undefined'))
|
||||||
if (rangeStr === undefined)
|
if (rangeStr === undefined)
|
||||||
return Promise.reject(new Error('rangeStr is undefined'))
|
return Promise.reject(new Error('rangeStr is undefined'))
|
||||||
if (commandStr === undefined)
|
if (commandStr === undefined) {
|
||||||
return Promise.reject(new Error('commandStr is undefined'))
|
return Promise.reject(new Error('commandStr is undefined'))
|
||||||
|
}
|
||||||
const range: SourceRange = JSON.parse(rangeStr)
|
const range: SourceRange = JSON.parse(rangeStr)
|
||||||
const command: EngineCommand = JSON.parse(commandStr)
|
const command: EngineCommand = JSON.parse(commandStr)
|
||||||
const idToRangeMap: { [key: string]: SourceRange } =
|
const idToRangeMap: { [key: string]: SourceRange } =
|
||||||
@ -2047,17 +1874,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setPlaneHidden(id: string, hidden: boolean) {
|
async setPlaneHidden(id: string, hidden: boolean) {
|
||||||
if (this.engineConnection === undefined) return
|
|
||||||
|
|
||||||
// Can't send commands if there's no connection
|
|
||||||
if (
|
|
||||||
this.engineConnection.state.type ===
|
|
||||||
EngineConnectionStateType.Disconnecting ||
|
|
||||||
this.engineConnection.state.type ===
|
|
||||||
EngineConnectionStateType.Disconnected
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
return await this.sendSceneCommand({
|
return await this.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
|
@ -63,7 +63,7 @@ export class CoreDumpManager {
|
|||||||
|
|
||||||
// Get the backend pool we've requested.
|
// Get the backend pool we've requested.
|
||||||
pool(): string {
|
pool(): string {
|
||||||
return this.engineCommandManager.settings.pool || ''
|
return this.engineCommandManager.pool || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the os information.
|
// Get the os information.
|
||||||
|
@ -104,9 +104,11 @@ export const fileLoader: LoaderFunction = async ({
|
|||||||
// the file system and not the editor.
|
// the file system and not the editor.
|
||||||
codeManager.updateCurrentFilePath(current_file_path)
|
codeManager.updateCurrentFilePath(current_file_path)
|
||||||
codeManager.updateCodeStateEditor(code)
|
codeManager.updateCodeStateEditor(code)
|
||||||
|
|
||||||
// We don't want to call await on execute code since we don't want to block the UI
|
// We don't want to call await on execute code since we don't want to block the UI
|
||||||
kclManager.executeCode(true)
|
kclManager.isFirstRender = true
|
||||||
|
kclManager.executeCode(true).then(() => {
|
||||||
|
kclManager.isFirstRender = false
|
||||||
|
})
|
||||||
|
|
||||||
// Set the file system manager to the project path
|
// Set the file system manager to the project path
|
||||||
// So that WASM gets an updated path for operations
|
// So that WASM gets an updated path for operations
|
||||||
|
@ -2,15 +2,6 @@ import { type Models } from '@kittycad/lib'
|
|||||||
import { Setting, settings } from './initialSettings'
|
import { Setting, settings } from './initialSettings'
|
||||||
import { AtLeast, PathValue, Paths } from 'lib/types'
|
import { AtLeast, PathValue, Paths } from 'lib/types'
|
||||||
import { CommandArgumentConfig } from 'lib/commandTypes'
|
import { CommandArgumentConfig } from 'lib/commandTypes'
|
||||||
import { Themes } from 'lib/theme'
|
|
||||||
|
|
||||||
export interface SettingsViaQueryString {
|
|
||||||
pool: string | null
|
|
||||||
theme: Themes
|
|
||||||
highlightEdges: boolean
|
|
||||||
enableSSAO: boolean
|
|
||||||
showScaleGrid: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum UnitSystem {
|
export enum UnitSystem {
|
||||||
Imperial = 'imperial',
|
Imperial = 'imperial',
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import { Program, ProgramMemory, _executor, SourceRange } from '../lang/wasm'
|
import { Program, ProgramMemory, _executor, SourceRange } from '../lang/wasm'
|
||||||
import {
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
EngineCommandManager,
|
|
||||||
EngineCommandManagerEvents,
|
|
||||||
} from 'lang/std/engineConnection'
|
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
@ -85,6 +82,7 @@ export async function enginelessExecutor(
|
|||||||
setIsStreamReady: () => {},
|
setIsStreamReady: () => {},
|
||||||
setMediaStream: () => {},
|
setMediaStream: () => {},
|
||||||
}) as any as EngineCommandManager
|
}) as any as EngineCommandManager
|
||||||
|
await mockEngineCommandManager.waitForReady
|
||||||
mockEngineCommandManager.startNewSession()
|
mockEngineCommandManager.startNewSession()
|
||||||
const programMemory = await _executor(ast, pm, mockEngineCommandManager, true)
|
const programMemory = await _executor(ast, pm, mockEngineCommandManager, true)
|
||||||
await mockEngineCommandManager.waitForAllCommands()
|
await mockEngineCommandManager.waitForAllCommands()
|
||||||
@ -101,6 +99,7 @@ export async function executor(
|
|||||||
setMediaStream: () => {},
|
setMediaStream: () => {},
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
|
executeCode: () => {},
|
||||||
makeDefaultPlanes: () => {
|
makeDefaultPlanes: () => {
|
||||||
return new Promise((resolve) => resolve(defaultPlanes))
|
return new Promise((resolve) => resolve(defaultPlanes))
|
||||||
},
|
},
|
||||||
@ -108,21 +107,9 @@ export async function executor(
|
|||||||
return new Promise((resolve) => resolve())
|
return new Promise((resolve) => resolve())
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
await engineCommandManager.waitForReady
|
||||||
return new Promise((resolve) => {
|
|
||||||
engineCommandManager.addEventListener(
|
|
||||||
EngineCommandManagerEvents.SceneReady,
|
|
||||||
async () => {
|
|
||||||
engineCommandManager.startNewSession()
|
engineCommandManager.startNewSession()
|
||||||
const programMemory = await _executor(
|
const programMemory = await _executor(ast, pm, engineCommandManager, false)
|
||||||
ast,
|
|
||||||
pm,
|
|
||||||
engineCommandManager,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands()
|
||||||
Promise.resolve(programMemory)
|
return programMemory
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user