Fix up everything after bumping kittycad/lib

This commit is contained in:
lee-at-zoo-corp
2025-03-20 16:39:16 -04:00
parent 2a60efc5ab
commit cfbe805e14
7 changed files with 106 additions and 253 deletions

View File

@ -105,12 +105,7 @@ export class CameraControls {
wasDragging: boolean
mouseDownPosition: Vector2
mouseNewPosition: Vector2
old:
| {
camera: PerspectiveCamera | OrthographicCamera
target: Vector3
}
| undefined
oldCameraState: undefined | CameraViewState_type
rotationSpeed = 0.3
enableRotate = true
enablePan = true
@ -281,7 +276,7 @@ export class CameraControls {
const cb = ({ data, type }: CallBackParam) => {
// We're reconnecting, so ignore this init proces.
if (this.old) {
if (this.oldCameraState) {
return
}
@ -969,26 +964,40 @@ export class CameraControls {
})
}
async restoreCameraPosition(): Promise<void> {
if (!this.old) return
async restoreRemoteCameraStateAndTriggerSync() {
if (!this.oldCameraState) return
this.camera = this.old.camera.clone()
this.target = this.old.target.clone()
void this.engineCommandManager.sendSceneCommand({
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
...convertThreeCamValuesToEngineCam({
isPerspective: true,
position: this.camera.position,
quaternion: this.camera.quaternion,
zoom: this.camera.zoom,
target: this.target,
}),
type: 'default_camera_set_view',
view: this.oldCameraState,
},
})
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
}
async saveRemoteCameraState() {
const cameraViewStateResponse = await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_get_view' },
})
if (!cameraViewStateResponse) return
if ('resp' in cameraViewStateResponse
&& 'modeling_response' in cameraViewStateResponse.resp.data
&& 'data' in cameraViewStateResponse.resp.data.modeling_response
&& 'view' in cameraViewStateResponse.resp.data.modeling_response.data) {
this.oldCameraState = cameraViewStateResponse.resp.data.modeling_response.data.view
}
}
async tweenCameraToQuaternion(

View File

@ -16,13 +16,16 @@ import { getArtifactOfTypes } from 'lang/std/artifactGraph'
import { ViewControlContextMenu } from './ViewControlMenu'
import { useSettings, engineStreamActor } from 'machines/appMachine'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { EngineStreamState, EngineStreamTransition } from 'machines/engineStreamMachine'
import {
EngineStreamState,
EngineStreamTransition,
} from 'machines/engineStreamMachine'
import { useSelector } from '@xstate/react'
import { REASONABLE_TIME_TO_REFRESH_STREAM_SIZE } from 'lib/timings'
export const EngineStream = (props: {
pool: string | null,
authToken: string | undefined,
pool: string | null
authToken: string | undefined
}) => {
const { setAppState } = useAppState()
@ -97,6 +100,22 @@ export const EngineStream = (props: {
}
}, [])
// In the past we'd try to play immediately, but the proper thing is to way
// for the 'canplay' event to tell us data is ready.
useEffect(() => {
const videoRef = engineStreamState.context.videoRef.current
if (!videoRef) {
return
}
const play = () => {
videoRef.play().catch(console.error)
}
videoRef.addEventListener('canplay', play)
return () => {
videoRef.removeEventListener('canplay', play)
}
}, [engineStreamState.context.videoRef.current])
useEffect(() => {
if (engineStreamState.value === EngineStreamState.Reconfiguring) return
const video = engineStreamState.context.videoRef?.current
@ -105,6 +124,9 @@ export const EngineStream = (props: {
if (!canvas) return
new ResizeObserver(() => {
// Prevents:
// `Uncaught ResizeObserver loop completed with undelivered notifications`
window.requestAnimationFrame(() => {
if (Date.now() - last.current < REASONABLE_TIME_TO_REFRESH_STREAM_SIZE)
return
last.current = Date.now()
@ -116,6 +138,7 @@ export const EngineStream = (props: {
timeoutStart.current = Date.now()
startOrReconfigureEngine()
}
})
}).observe(document.body)
}, [engineStreamState.value])
@ -262,7 +285,7 @@ export const EngineStream = (props: {
if (btnName(e.nativeEvent).left) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sendSelectEventToEngine(e, engineStreamState.context.videoRef.current)
sendSelectEventToEngine(e)
}
}
@ -284,7 +307,7 @@ export const EngineStream = (props: {
return
}
sendSelectEventToEngine(e, engineStreamState.context.videoRef.current)
sendSelectEventToEngine(e)
.then(({ entity_id }) => {
if (!entity_id) {
// No entity selected. This is benign

View File

@ -1,183 +0,0 @@
import { useAppState, useAppStream } from '@src/AppState'
import { useEffect, useLayoutEffect, useRef } from 'react'
import type { useModelingContext } from '@src/hooks/useModelingContext'
import { useNetworkContext } from '@src/hooks/useNetworkContext'
import {
DisconnectingType,
EngineConnectionStateType,
} from '@src/lang/std/engineConnection'
import type { SettingsViaQueryString } from '@src/lib/settings/settingsTypes'
import { engineCommandManager } from '@src/lib/singletons'
import { Themes } from '@src/lib/theme'
import { deferExecution } from '@src/lib/utils'
export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>,
modelingSend: ReturnType<typeof useModelingContext>['send'],
modelingContext: ReturnType<typeof useModelingContext>['context'],
settings: SettingsViaQueryString = {
pool: null,
theme: Themes.System,
highlightEdges: true,
enableSSAO: true,
showScaleGrid: false,
cameraProjection: 'perspective',
cameraOrbit: 'spherical',
},
token?: string
) {
const networkContext = useNetworkContext()
const { pingPongHealth, immediateState } = networkContext
const { setAppState } = useAppState()
const { setMediaStream } = useAppStream()
const hasSetNonZeroDimensions = useRef<boolean>(false)
if (settings.pool) {
// override the pool param (?pool=) to request a specific engine instance
// from a particular pool.
engineCommandManager.settings.pool = settings.pool
}
const startEngineInstance = () => {
// Load the engine command manager once with the initial width and height,
// then we do not want to reload it.
const { width: quadWidth, height: quadHeight } = getDimensions(
streamRef?.current?.offsetWidth ?? 0,
streamRef?.current?.offsetHeight ?? 0
)
engineCommandManager.start({
setMediaStream: (mediaStream) => setMediaStream(mediaStream),
setIsStreamReady: (isStreamReady) => setAppState({ isStreamReady }),
width: quadWidth,
height: quadHeight,
token,
settings,
})
hasSetNonZeroDimensions.current = true
}
useLayoutEffect(() => {
const { width: quadWidth, height: quadHeight } = getDimensions(
streamRef?.current?.offsetWidth ?? 0,
streamRef?.current?.offsetHeight ?? 0
)
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
startEngineInstance()
}
}, [
streamRef?.current?.offsetWidth,
streamRef?.current?.offsetHeight,
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(() => {
engineCommandManager.settings = settings
const handleResize = deferExecution(() => {
engineCommandManager.handleResize(
getDimensions(
streamRef?.current?.offsetWidth ?? 0,
streamRef?.current?.offsetHeight ?? 0
)
)
}, 500)
const onOnline = () => {
startEngineInstance()
}
const onVisibilityChange = () => {
if (window.document.visibilityState === 'visible') {
if (
!engineCommandManager.engineConnection?.isReady() &&
!engineCommandManager.engineConnection?.isConnecting()
) {
startEngineInstance()
}
}
}
window.document.addEventListener('visibilitychange', onVisibilityChange)
const onAnyInput = () => {
const isEngineNotReadyOrConnecting =
!engineCommandManager.engineConnection?.isReady() &&
!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()
}
}
window.document.addEventListener('keydown', onAnyInput)
window.document.addEventListener('mousemove', onAnyInput)
window.document.addEventListener('mousedown', onAnyInput)
window.document.addEventListener('scroll', onAnyInput)
window.document.addEventListener('touchstart', onAnyInput)
const onOffline = () => {
engineCommandManager.tearDown()
}
window.addEventListener('online', onOnline)
window.addEventListener('offline', onOffline)
window.addEventListener('resize', handleResize)
return () => {
window.document.removeEventListener(
'visibilitychange',
onVisibilityChange
)
window.document.removeEventListener('keydown', onAnyInput)
window.document.removeEventListener('mousemove', onAnyInput)
window.document.removeEventListener('mousedown', onAnyInput)
window.document.removeEventListener('scroll', onAnyInput)
window.document.removeEventListener('touchstart', onAnyInput)
window.removeEventListener('online', onOnline)
window.removeEventListener('offline', onOffline)
window.removeEventListener('resize', handleResize)
}
// Engine relies on many settings so we should rebind events when it changes
// We have to list out the ones we care about because the settings object holds
// non-settings too...
}, [...Object.values(settings)])
}
function getDimensions(streamWidth?: number, streamHeight?: number) {
const factorOf = 4
const maxResolution = 2000
const width = streamWidth ? streamWidth : 0
const height = streamHeight ? streamHeight : 0
const ratio = Math.min(
Math.min(maxResolution / width, maxResolution / height),
1.0
)
const quadWidth = Math.round((width * ratio) / factorOf) * factorOf
const quadHeight = Math.round((height * ratio) / factorOf) * factorOf
return { width: quadWidth, height: quadHeight }
}

View File

@ -839,13 +839,6 @@ class EngineConnection extends EventTarget {
this.engineCommandManager.inSequence = 1
// Bust the cache before anything
;(async () => {
await rustContext.clearSceneAndBustCache(
kclManager.engineCommandManager.settings
)
})().catch(reportRejection)
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.Opened, { detail: this })
)

View File

@ -43,11 +43,10 @@ describe(`testing settings initialization`, () => {
},
},
}
const projectConfiguration: DeepPartial<Configuration> = {
const projectConfiguration: DeepPartial<ProjectConfiguration> = {
settings: {
app: {
appearance: {
theme: 'light',
color: 200,
},
},
@ -82,11 +81,10 @@ describe(`testing getAllCurrentSettings`, () => {
},
},
}
const projectConfiguration: DeepPartial<Configuration> = {
const projectConfiguration: DeepPartial<ProjectConfiguration> = {
settings: {
app: {
appearance: {
theme: 'light',
color: 200,
},
},

View File

@ -68,6 +68,6 @@ export const useSettings = () =>
return settings
})
export const engineStreamActor = appActor.system.get(ENGINE_STREAM) as ActorRefFrom<
typeof engineStreamMachine
>
export const engineStreamActor = appActor.system.get(
ENGINE_STREAM
) as ActorRefFrom<typeof engineStreamMachine>

View File

@ -1,7 +1,13 @@
import { jsAppSettings } from 'lang/wasm'
import { uuidv4 } from 'lib/utils'
import { MutableRefObject } from 'react'
import { setup, assign, fromPromise } from 'xstate'
import { rustContext, kclManager, sceneInfra, engineCommandManager } from 'lib/singletons'
import {
rustContext,
kclManager,
sceneInfra,
engineCommandManager,
} from 'lib/singletons'
import { trap } from 'lib/trap'
import { Vector3, Vector4 } from 'three'
@ -94,13 +100,28 @@ export const engineStreamMachine = setup({
const mediaStream = context.mediaStream
if (!mediaStream) return false
// If the video is already playing it means we're doing a reconfigure.
// We don't want to re-run the KCL or touch the video element at all.
if (!video.paused) {
return
}
await sceneInfra.camControls.restoreRemoteCameraStateAndTriggerSync()
video.style.display = 'block'
canvas.style.display = 'none'
// await sceneInfra.camControls.restoreCameraPosition()
video.srcObject = mediaStream
await video.play()
// Bust the cache before trying to execute since this may
// be a reconnection and if cache is not cleared, it
// will not reexecute.
// When calling cache before _any_ executions it errors, but non-fatal.
await rustContext
.clearSceneAndBustCache(
{ settings: await jsAppSettings() },
)
.catch(console.warn)
await kclManager.executeCode(params.zoomToFit)
}
@ -120,19 +141,21 @@ export const engineStreamMachine = setup({
if (!canvas) return
await holdOntoVideoFrameInCanvas(video, canvas)
video.style.display = 'none'
await sceneInfra.camControls.saveRemoteCameraState()
// Make sure we're on the next frame for no flickering between canvas
// and the video elements.
window.requestAnimationFrame(
() =>
void (async () => {
video.style.display = 'none'
// Destroy the media stream. We will re-establish it. We could
// leave everything at pausing, preventing video decoders from running
// but we can do even better by significantly reducing network
// cards also.
context.mediaStream?.getVideoTracks()[0].stop()
context.mediaStream = null
video.srcObject = null
engineCommandManager.tearDown({ idleMode: true })
@ -171,18 +194,10 @@ export const engineStreamMachine = setup({
engineCommandManager.settings = settingsNext
// If we don't pause there could be a really bad flicker
// on reconfiguration (resize, for example)
await holdOntoVideoFrameInCanvas(video, canvas)
canvas.style.display = 'block'
window.requestAnimationFrame(() => {
video.style.display = 'none'
engineCommandManager.start({
setMediaStream: event.onMediaStream,
setIsStreamReady: (isStreamReady) => {
video.style.display = 'block'
canvas.style.display = 'none'
event.setAppState({ isStreamReady })
},
width,
@ -212,13 +227,11 @@ export const engineStreamMachine = setup({
reenter: true,
on: {
[EngineStreamTransition.SetPool]: {
target: EngineStreamState.Setup,
actions: [
assign({ pool: ({ context, event }) => event.data.pool }),
],
target: EngineStreamState.Off,
actions: [assign({ pool: ({ context, event }) => event.data.pool })],
},
[EngineStreamTransition.SetAuthToken]: {
target: EngineStreamState.Setup,
target: EngineStreamState.Off,
actions: [
assign({ authToken: ({ context, event }) => event.data.authToken }),
],