Persist theme - Reload everything on a disconnect (#3250)
* Reload everything on a disconnect * fix unit-integration tests * Further improvements to connection manager; persist theme across reconnects * Fix up artifactGraph.test * Actually pass the callback * Kurt hmmm (#3308) * kurts attempts * we're almost sane * get tests working, praise be --------- Co-authored-by: 49lf <ircsurfer33@gmail.com> * typo --------- Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { EngineCommandManagerEvents } from 'lang/std/engineConnection'
|
||||
import { engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||
import { throttle, isReducedMotion } from 'lib/utils'
|
||||
|
||||
@ -13,9 +14,12 @@ export const CamToggle = () => {
|
||||
const [enableRotate, setEnableRotate] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
engineCommandManager.waitForReady.then(async () => {
|
||||
sceneInfra.camControls.dollyZoom(fov)
|
||||
})
|
||||
engineCommandManager.addEventListener(
|
||||
EngineCommandManagerEvents.SceneReady,
|
||||
async () => {
|
||||
sceneInfra.camControls.dollyZoom(fov)
|
||||
}
|
||||
)
|
||||
}, [])
|
||||
|
||||
const toggleCamera = () => {
|
||||
|
@ -176,6 +176,7 @@ const FileTreeItem = ({
|
||||
)
|
||||
codeManager.writeToFile()
|
||||
|
||||
// Prevent seeing the model built one piece at a time when changing files
|
||||
kclManager.isFirstRender = true
|
||||
kclManager.executeCode(true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
|
@ -47,7 +47,7 @@ const Loading = ({ children }: React.PropsWithChildren) => {
|
||||
onConnectionStateChange as EventListener
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
}, [engineCommandManager, engineCommandManager.engineConnection])
|
||||
|
||||
useEffect(() => {
|
||||
// Don't set long loading time if there's a more severe error
|
||||
|
@ -78,7 +78,12 @@ import { err, trap } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { modelingMachineEvent } from 'editor/manager'
|
||||
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
|
||||
import { ExportIntent } from 'lang/std/engineConnection'
|
||||
import {
|
||||
ExportIntent,
|
||||
EngineConnectionState,
|
||||
EngineConnectionStateType,
|
||||
EngineConnectionEvents,
|
||||
} from 'lang/std/engineConnection'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -154,7 +159,10 @@ export const ModelingMachineProvider = ({
|
||||
sceneInfra.camControls.syncDirection = 'engineToClient'
|
||||
|
||||
store.videoElement?.pause()
|
||||
|
||||
kclManager.isFirstRender = true
|
||||
kclManager.executeCode().then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
if (engineCommandManager.engineConnection?.idleMode) return
|
||||
|
||||
store.videoElement?.play().catch((e) => {
|
||||
@ -909,15 +917,19 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
)
|
||||
|
||||
useSetupEngineManager(streamRef, token, {
|
||||
pool: pool,
|
||||
theme: theme.current,
|
||||
highlightEdges: highlightEdges.current,
|
||||
enableSSAO: enableSSAO.current,
|
||||
useSetupEngineManager(
|
||||
streamRef,
|
||||
modelingSend,
|
||||
modelingContext: modelingState.context,
|
||||
showScaleGrid: showScaleGrid.current,
|
||||
})
|
||||
modelingState.context,
|
||||
{
|
||||
pool: pool,
|
||||
theme: theme.current,
|
||||
highlightEdges: highlightEdges.current,
|
||||
enableSSAO: enableSSAO.current,
|
||||
showScaleGrid: showScaleGrid.current,
|
||||
},
|
||||
token
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
kclManager.registerExecuteCallback(() => {
|
||||
@ -945,17 +957,25 @@ export const ModelingMachineProvider = ({
|
||||
}, [modelingState.context.selectionRanges])
|
||||
|
||||
useEffect(() => {
|
||||
const offlineCallback = () => {
|
||||
const onConnectionStateChanged = ({ detail }: CustomEvent) => {
|
||||
// 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
|
||||
// this then.
|
||||
modelingSend({ type: 'Cancel' })
|
||||
if (detail.type === EngineConnectionStateType.Disconnecting) {
|
||||
modelingSend({ type: 'Cancel' })
|
||||
}
|
||||
}
|
||||
window.addEventListener('offline', offlineCallback)
|
||||
engineCommandManager.engineConnection?.addEventListener(
|
||||
EngineConnectionEvents.ConnectionStateChanged,
|
||||
onConnectionStateChanged as EventListener
|
||||
)
|
||||
return () => {
|
||||
window.removeEventListener('offline', offlineCallback)
|
||||
engineCommandManager.engineConnection?.removeEventListener(
|
||||
EngineConnectionEvents.ConnectionStateChanged,
|
||||
onConnectionStateChanged as EventListener
|
||||
)
|
||||
}
|
||||
}, [modelingSend])
|
||||
}, [engineCommandManager.engineConnection, modelingSend])
|
||||
|
||||
// Allow using the delete key to delete solids
|
||||
useHotkeys(['backspace', 'delete', 'del'], () => {
|
||||
|
@ -64,13 +64,6 @@ export const KclEditorPane = () => {
|
||||
: context.app.theme.current
|
||||
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
|
||||
// with the wrapper.
|
||||
useHotkeys('mod+z', (e) => {
|
||||
|
@ -191,6 +191,7 @@ export const SettingsAuthProviderBase = ({
|
||||
allSettingsIncludesUnitChange ||
|
||||
resetSettingsIncludesUnitChange
|
||||
) {
|
||||
// Unit changes requires a re-exec of code
|
||||
kclManager.isFirstRender = true
|
||||
kclManager.executeCode(true).then(() => {
|
||||
kclManager.isFirstRender = false
|
||||
|
@ -11,21 +11,27 @@ import { sendSelectEventToEngine } from 'lib/selections'
|
||||
import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||
import { useAppStream } from 'AppState'
|
||||
import {
|
||||
EngineCommandManagerEvents,
|
||||
EngineConnectionStateType,
|
||||
DisconnectingType,
|
||||
} from 'lang/std/engineConnection'
|
||||
|
||||
enum StreamState {
|
||||
Playing = 'playing',
|
||||
Paused = 'paused',
|
||||
Resuming = 'resuming',
|
||||
Unset = 'unset',
|
||||
}
|
||||
|
||||
export const Stream = () => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isFirstRender, setIsFirstRender] = useState(kclManager.isFirstRender)
|
||||
const [clickCoords, setClickCoords] = useState<{ x: number; y: number }>()
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const { mediaStream } = useAppStream()
|
||||
const { overallState, immediateState } = useNetworkContext()
|
||||
const [isFreezeFrame, setIsFreezeFrame] = useState(false)
|
||||
const [isPaused, setIsPaused] = useState(false)
|
||||
const [streamState, setStreamState] = useState(StreamState.Unset)
|
||||
|
||||
const IDLE = settings.context.app.streamIdleMode.current
|
||||
|
||||
@ -38,10 +44,7 @@ export const Stream = () => {
|
||||
immediateState.type === EngineConnectionStateType.Disconnecting &&
|
||||
immediateState.value.type === DisconnectingType.Pause
|
||||
) {
|
||||
setIsPaused(true)
|
||||
}
|
||||
if (immediateState.type === EngineConnectionStateType.Connecting) {
|
||||
setIsPaused(false)
|
||||
setStreamState(StreamState.Paused)
|
||||
}
|
||||
}, [immediateState])
|
||||
|
||||
@ -76,8 +79,11 @@ export const Stream = () => {
|
||||
let timeoutIdIdleA: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
const teardown = () => {
|
||||
// Already paused
|
||||
if (streamState === StreamState.Paused) return
|
||||
|
||||
videoRef.current?.pause()
|
||||
setIsFreezeFrame(true)
|
||||
setStreamState(StreamState.Paused)
|
||||
sceneInfra.modelingSend({ type: 'Cancel' })
|
||||
// Give video time to pause
|
||||
window.requestAnimationFrame(() => {
|
||||
@ -91,7 +97,7 @@ export const Stream = () => {
|
||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
||||
} else if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
setStreamState(StreamState.Resuming)
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,10 +112,15 @@ export const Stream = () => {
|
||||
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
const onAnyInput = () => {
|
||||
// Clear both timers
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
clearTimeout(timeoutIdIdleB)
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
if (streamState === StreamState.Playing) {
|
||||
// Clear both timers
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
clearTimeout(timeoutIdIdleB)
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
}
|
||||
if (streamState === StreamState.Paused) {
|
||||
setStreamState(StreamState.Resuming)
|
||||
}
|
||||
}
|
||||
|
||||
if (IDLE) {
|
||||
@ -124,7 +135,27 @@ export const Stream = () => {
|
||||
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 () => {
|
||||
engineCommandManager.removeEventListener(
|
||||
EngineCommandManagerEvents.SceneReady,
|
||||
onSceneReady
|
||||
)
|
||||
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
||||
capture: true,
|
||||
})
|
||||
@ -152,19 +183,7 @@ export const Stream = () => {
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [IDLE])
|
||||
|
||||
useEffect(() => {
|
||||
setIsFirstRender(kclManager.isFirstRender)
|
||||
if (!kclManager.isFirstRender)
|
||||
setTimeout(() =>
|
||||
// execute in the next event loop
|
||||
videoRef.current?.play().catch((e) => {
|
||||
console.warn('Video playing was prevented', e, videoRef.current)
|
||||
})
|
||||
)
|
||||
setIsFreezeFrame(!kclManager.isFirstRender)
|
||||
}, [kclManager.isFirstRender])
|
||||
}, [IDLE, streamState])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@ -288,7 +307,8 @@ export const Stream = () => {
|
||||
<ClientSideScene
|
||||
cameraControls={settings.context.modeling.mouseControls.current}
|
||||
/>
|
||||
{isPaused && (
|
||||
{(streamState === StreamState.Paused ||
|
||||
streamState === StreamState.Resuming) && (
|
||||
<div className="text-center absolute inset-0">
|
||||
<div
|
||||
className="flex flex-col items-center justify-center h-screen"
|
||||
@ -310,16 +330,19 @@ export const Stream = () => {
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-base mt-2 text-primary bold">Paused</p>
|
||||
<p className="text-base mt-2 text-primary bold">
|
||||
{streamState === StreamState.Paused && 'Paused'}
|
||||
{streamState === StreamState.Resuming && 'Resuming'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(!isNetworkOkay || isLoading || isFirstRender) && !isFreezeFrame && (
|
||||
{(!isNetworkOkay || isLoading || kclManager.isFirstRender) && (
|
||||
<div className="text-center absolute inset-0">
|
||||
<Loading>
|
||||
{!isNetworkOkay && !isLoading ? (
|
||||
{!isNetworkOkay && !isLoading && !kclManager.isFirstRender ? (
|
||||
<span data-testid="loading-stream">Stream disconnected...</span>
|
||||
) : !isLoading && isFirstRender ? (
|
||||
) : !isLoading && kclManager.isFirstRender ? (
|
||||
<span data-testid="loading-stream">Building scene...</span>
|
||||
) : (
|
||||
<span data-testid="loading-stream">Loading stream...</span>
|
||||
|
Reference in New Issue
Block a user