Move engineStreamMachine as a global actor; tons of more work
This commit is contained in:
		
							
								
								
									
										34
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								src/App.tsx
									
									
									
									
									
								
							@ -41,7 +41,6 @@ import { onboardingPaths } from '@src/routes/Onboarding/paths'
 | 
			
		||||
maybeWriteToDisk()
 | 
			
		||||
  .then(() => {})
 | 
			
		||||
  .catch(() => {})
 | 
			
		||||
import EngineStreamContext from 'hooks/useEngineStreamContext'
 | 
			
		||||
import { EngineStream } from 'components/EngineStream'
 | 
			
		||||
 | 
			
		||||
export function App() {
 | 
			
		||||
@ -67,8 +66,6 @@ export function App() {
 | 
			
		||||
  const ref = useRef<HTMLDivElement>(null)
 | 
			
		||||
 | 
			
		||||
  // Stream related refs and data
 | 
			
		||||
  const videoRef = useRef<HTMLVideoElement>(null)
 | 
			
		||||
  const canvasRef = useRef<HTMLCanvasElement>(null)
 | 
			
		||||
  let [searchParams] = useSearchParams()
 | 
			
		||||
  const pool = searchParams.get('pool')
 | 
			
		||||
 | 
			
		||||
@ -86,7 +83,7 @@ export function App() {
 | 
			
		||||
  useHotKeyListener()
 | 
			
		||||
 | 
			
		||||
  const settings = useSettings()
 | 
			
		||||
  const token = useToken()
 | 
			
		||||
  const authToken = useToken()
 | 
			
		||||
 | 
			
		||||
  const coreDumpManager = useMemo(
 | 
			
		||||
    () =>
 | 
			
		||||
@ -94,7 +91,7 @@ export function App() {
 | 
			
		||||
        engineCommandManager,
 | 
			
		||||
        codeManager,
 | 
			
		||||
        rustContext,
 | 
			
		||||
        token
 | 
			
		||||
        authToken
 | 
			
		||||
      ),
 | 
			
		||||
    []
 | 
			
		||||
  )
 | 
			
		||||
@ -157,26 +154,13 @@ export function App() {
 | 
			
		||||
      />
 | 
			
		||||
      <ModalContainer />
 | 
			
		||||
      <ModelingSidebar paneOpacity={paneOpacity} />
 | 
			
		||||
      <EngineStreamContext.Provider
 | 
			
		||||
        options={{
 | 
			
		||||
          input: {
 | 
			
		||||
            videoRef,
 | 
			
		||||
            canvasRef,
 | 
			
		||||
            mediaStream: null,
 | 
			
		||||
            authToken: token,
 | 
			
		||||
            pool,
 | 
			
		||||
            zoomToFit: true,
 | 
			
		||||
          },
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <EngineStream />
 | 
			
		||||
        {/* <CamToggle /> */}
 | 
			
		||||
        <LowerRightControls coreDumpManager={coreDumpManager}>
 | 
			
		||||
          <UnitsMenu />
 | 
			
		||||
          <Gizmo />
 | 
			
		||||
          <CameraProjectionToggle />
 | 
			
		||||
        </LowerRightControls>
 | 
			
		||||
      </EngineStreamContext.Provider>
 | 
			
		||||
      <EngineStream pool={pool} authToken={authToken} />
 | 
			
		||||
      {/* <CamToggle /> */}
 | 
			
		||||
      <LowerRightControls coreDumpManager={coreDumpManager}>
 | 
			
		||||
        <UnitsMenu />
 | 
			
		||||
        <Gizmo />
 | 
			
		||||
        <CameraProjectionToggle />
 | 
			
		||||
      </LowerRightControls>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -480,12 +480,13 @@ export class CameraControls {
 | 
			
		||||
      if (this.syncDirection === 'engineToClient') {
 | 
			
		||||
        const newCmdId = uuidv4()
 | 
			
		||||
 | 
			
		||||
        const { videoRef } = engineStreamActor.getSnapshot().context
 | 
			
		||||
        // Nonsense to do anything until the video stream is established.
 | 
			
		||||
        if (!this.engineCommandManager.elVideo) return
 | 
			
		||||
        if (!videoRef.current) return
 | 
			
		||||
 | 
			
		||||
        const { x, y } = getNormalisedCoordinates(
 | 
			
		||||
          event,
 | 
			
		||||
          this.engineCommandManager.elVideo,
 | 
			
		||||
          videoRef.current,
 | 
			
		||||
          this.engineCommandManager.streamDimensions
 | 
			
		||||
        )
 | 
			
		||||
        this.throttledEngCmd({
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import { MouseEventHandler, useEffect, useRef } from 'react'
 | 
			
		||||
import { useAppState } from 'AppState'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import { useModelingContext } from 'hooks/useModelingContext'
 | 
			
		||||
import { useNetworkContext } from 'hooks/useNetworkContext'
 | 
			
		||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
 | 
			
		||||
@ -14,31 +13,34 @@ import { PATHS } from 'lib/paths'
 | 
			
		||||
import { IndexLoaderData } from 'lib/types'
 | 
			
		||||
import { err, reportRejection, trap } from 'lib/trap'
 | 
			
		||||
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
 | 
			
		||||
import { clearSceneAndBustCache } from 'lang/wasm'
 | 
			
		||||
import { ViewControlContextMenu } from './ViewControlMenu'
 | 
			
		||||
import { useSettings, engineStreamActor } from 'machines/appMachine'
 | 
			
		||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
 | 
			
		||||
import { EngineStreamState, EngineStreamTransition } from 'machines/engineStreamMachine'
 | 
			
		||||
import { useSelector } from '@xstate/react'
 | 
			
		||||
import useEngineStreamContext, {
 | 
			
		||||
  EngineStreamState,
 | 
			
		||||
  EngineStreamTransition,
 | 
			
		||||
} from 'hooks/useEngineStreamContext'
 | 
			
		||||
import { REASONABLE_TIME_TO_REFRESH_STREAM_SIZE } from 'lib/timings'
 | 
			
		||||
 | 
			
		||||
export const EngineStream = () => {
 | 
			
		||||
export const EngineStream = (props: {
 | 
			
		||||
  pool: string | null,
 | 
			
		||||
  authToken: string | undefined,
 | 
			
		||||
}) => {
 | 
			
		||||
  const { setAppState } = useAppState()
 | 
			
		||||
 | 
			
		||||
  const { overallState } = useNetworkContext()
 | 
			
		||||
  const { settings } = useSettingsAuthContext()
 | 
			
		||||
  const settings = useSettings()
 | 
			
		||||
 | 
			
		||||
  const engineStreamState = useSelector(engineStreamActor, (state) => state)
 | 
			
		||||
 | 
			
		||||
  const { file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
 | 
			
		||||
  const last = useRef<number>(Date.now())
 | 
			
		||||
  const videoWrapperRef = useRef<HTMLDivElement>(null)
 | 
			
		||||
 | 
			
		||||
  const settingsEngine = {
 | 
			
		||||
    theme: settings.context.app.theme.current,
 | 
			
		||||
    enableSSAO: settings.context.app.enableSSAO.current,
 | 
			
		||||
    highlightEdges: settings.context.modeling.highlightEdges.current,
 | 
			
		||||
    showScaleGrid: settings.context.modeling.showScaleGrid.current,
 | 
			
		||||
    cameraProjection: settings.context.modeling.cameraProjection.current,
 | 
			
		||||
    theme: settings.app.theme.current,
 | 
			
		||||
    enableSSAO: settings.modeling.enableSSAO.current,
 | 
			
		||||
    highlightEdges: settings.modeling.highlightEdges.current,
 | 
			
		||||
    showScaleGrid: settings.modeling.showScaleGrid.current,
 | 
			
		||||
    cameraProjection: settings.modeling.cameraProjection.current,
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { state: modelingMachineState, send: modelingMachineActorSend } =
 | 
			
		||||
@ -46,10 +48,7 @@ export const EngineStream = () => {
 | 
			
		||||
 | 
			
		||||
  const commandBarState = useCommandBarState()
 | 
			
		||||
 | 
			
		||||
  const engineStreamActor = useEngineStreamContext.useActorRef()
 | 
			
		||||
  const engineStreamState = engineStreamActor.getSnapshot()
 | 
			
		||||
 | 
			
		||||
  const streamIdleMode = settings.context.app.streamIdleMode.current
 | 
			
		||||
  const streamIdleMode = settings.app.streamIdleMode.current
 | 
			
		||||
 | 
			
		||||
  const startOrReconfigureEngine = () => {
 | 
			
		||||
    engineStreamActor.send({
 | 
			
		||||
@ -80,6 +79,15 @@ export const EngineStream = () => {
 | 
			
		||||
      play
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    engineStreamActor.send({
 | 
			
		||||
      type: EngineStreamTransition.SetPool,
 | 
			
		||||
      data: { pool: props.pool },
 | 
			
		||||
    })
 | 
			
		||||
    engineStreamActor.send({
 | 
			
		||||
      type: EngineStreamTransition.SetAuthToken,
 | 
			
		||||
      data: { authToken: props.authToken },
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      engineCommandManager.tearDown()
 | 
			
		||||
      engineCommandManager.removeEventListener(
 | 
			
		||||
@ -325,7 +333,7 @@ export const EngineStream = () => {
 | 
			
		||||
        No canvas support
 | 
			
		||||
      </canvas>
 | 
			
		||||
      <ClientSideScene
 | 
			
		||||
        cameraControls={settings.context.modeling.mouseControls.current}
 | 
			
		||||
        cameraControls={settings.modeling.mouseControls.current}
 | 
			
		||||
      />
 | 
			
		||||
      <ViewControlContextMenu
 | 
			
		||||
        event="mouseup"
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,7 @@ export const ModelStateIndicator = () => {
 | 
			
		||||
  const [commands] = useEngineCommands()
 | 
			
		||||
  const [isDone, setIsDone] = useState<boolean>(false)
 | 
			
		||||
 | 
			
		||||
  const engineStreamActor = useEngineStreamContext.useActorRef()
 | 
			
		||||
  const engineStreamState = engineStreamActor.getSnapshot()
 | 
			
		||||
  const engineStreamState = useSelector(engineStreamActor, (state) => state)
 | 
			
		||||
 | 
			
		||||
  const lastCommandType = commands[commands.length - 1]?.type
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -147,7 +147,6 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
      showScaleGrid,
 | 
			
		||||
      cameraOrbit,
 | 
			
		||||
      enableSSAO,
 | 
			
		||||
      cameraProjection,
 | 
			
		||||
    },
 | 
			
		||||
  } = useSettings()
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
 | 
			
		||||
@ -1,414 +0,0 @@
 | 
			
		||||
import { useAppStream } from '@src/AppState'
 | 
			
		||||
import type { MouseEventHandler } from 'react'
 | 
			
		||||
import { useEffect, useRef, useState } from 'react'
 | 
			
		||||
import { useRouteLoaderData } from 'react-router-dom'
 | 
			
		||||
 | 
			
		||||
import { ClientSideScene } from '@src/clientSideScene/ClientSideSceneComp'
 | 
			
		||||
import Loading from '@src/components/Loading'
 | 
			
		||||
import { ViewControlContextMenu } from '@src/components/ViewControlMenu'
 | 
			
		||||
import { useModelingContext } from '@src/hooks/useModelingContext'
 | 
			
		||||
import { useNetworkContext } from '@src/hooks/useNetworkContext'
 | 
			
		||||
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
 | 
			
		||||
import { getArtifactOfTypes } from '@src/lang/std/artifactGraph'
 | 
			
		||||
import {
 | 
			
		||||
  DisconnectingType,
 | 
			
		||||
  EngineCommandManagerEvents,
 | 
			
		||||
  EngineConnectionStateType,
 | 
			
		||||
} from '@src/lang/std/engineConnection'
 | 
			
		||||
import { btnName } from '@src/lib/cameraControls'
 | 
			
		||||
import { PATHS } from '@src/lib/paths'
 | 
			
		||||
import { sendSelectEventToEngine } from '@src/lib/selections'
 | 
			
		||||
import {
 | 
			
		||||
  engineCommandManager,
 | 
			
		||||
  kclManager,
 | 
			
		||||
  sceneInfra,
 | 
			
		||||
} from '@src/lib/singletons'
 | 
			
		||||
import { err, reportRejection } from '@src/lib/trap'
 | 
			
		||||
import type { IndexLoaderData } from '@src/lib/types'
 | 
			
		||||
import { uuidv4 } from '@src/lib/utils'
 | 
			
		||||
import { useSettings } from '@src/machines/appMachine'
 | 
			
		||||
import { useCommandBarState } from '@src/machines/commandBarMachine'
 | 
			
		||||
 | 
			
		||||
enum StreamState {
 | 
			
		||||
  Playing = 'playing',
 | 
			
		||||
  Paused = 'paused',
 | 
			
		||||
  Resuming = 'resuming',
 | 
			
		||||
  Unset = 'unset',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const Stream = () => {
 | 
			
		||||
  const [isLoading, setIsLoading] = useState(true)
 | 
			
		||||
  const videoWrapperRef = useRef<HTMLDivElement>(null)
 | 
			
		||||
  const videoRef = useRef<HTMLVideoElement>(null)
 | 
			
		||||
  const settings = useSettings()
 | 
			
		||||
  const { state, send } = useModelingContext()
 | 
			
		||||
  const commandBarState = useCommandBarState()
 | 
			
		||||
  const { mediaStream } = useAppStream()
 | 
			
		||||
  const { overallState, immediateState } = useNetworkContext()
 | 
			
		||||
  const [streamState, setStreamState] = useState(StreamState.Unset)
 | 
			
		||||
  const { file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
 | 
			
		||||
 | 
			
		||||
  const IDLE = settings.app.streamIdleMode.current
 | 
			
		||||
 | 
			
		||||
  const isNetworkOkay =
 | 
			
		||||
    overallState === NetworkHealthState.Ok ||
 | 
			
		||||
    overallState === NetworkHealthState.Weak
 | 
			
		||||
 | 
			
		||||
  engineCommandManager.elVideo = videoRef.current
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute code and show a "building scene message"
 | 
			
		||||
   * in Stream.tsx in the meantime.
 | 
			
		||||
   *
 | 
			
		||||
   * I would like for this to live somewhere more central,
 | 
			
		||||
   * but it seems to me that we need the video element ref
 | 
			
		||||
   * to be able to play the video after the code has been
 | 
			
		||||
   * executed. If we can find a way to do this from a more
 | 
			
		||||
   * central place, we can move this code there.
 | 
			
		||||
   */
 | 
			
		||||
  function executeCodeAndPlayStream() {
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | 
			
		||||
    kclManager.executeCode().then(async () => {
 | 
			
		||||
      await videoRef.current?.play().catch((e) => {
 | 
			
		||||
        console.warn('Video playing was prevented', e, videoRef.current)
 | 
			
		||||
      })
 | 
			
		||||
      setStreamState(StreamState.Playing)
 | 
			
		||||
 | 
			
		||||
      // Only call zoom_to_fit once when the stream starts to center the scene.
 | 
			
		||||
      await engineCommandManager.sendSceneCommand({
 | 
			
		||||
        type: 'modeling_cmd_req',
 | 
			
		||||
        cmd_id: uuidv4(),
 | 
			
		||||
        cmd: {
 | 
			
		||||
          type: 'zoom_to_fit',
 | 
			
		||||
          object_ids: [], // leave empty to zoom to all objects
 | 
			
		||||
          padding: 0.1, // padding around the objects
 | 
			
		||||
          animated: false, // don't animate the zoom for now
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Subscribe to execute code when the file changes
 | 
			
		||||
   * but only if the scene is already ready.
 | 
			
		||||
   * See onSceneReady for the initial scene setup.
 | 
			
		||||
   */
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (engineCommandManager.engineConnection?.isReady() && file?.path) {
 | 
			
		||||
      console.log('execute on file change')
 | 
			
		||||
      executeCodeAndPlayStream()
 | 
			
		||||
    }
 | 
			
		||||
  }, [file?.path, engineCommandManager.engineConnection])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (
 | 
			
		||||
      immediateState.type === EngineConnectionStateType.Disconnecting &&
 | 
			
		||||
      immediateState.value.type === DisconnectingType.Pause
 | 
			
		||||
    ) {
 | 
			
		||||
      setStreamState(StreamState.Paused)
 | 
			
		||||
    }
 | 
			
		||||
  }, [immediateState])
 | 
			
		||||
 | 
			
		||||
  // Linux has a default behavior to paste text on middle mouse up
 | 
			
		||||
  // This adds a listener to block that pasting if the click target
 | 
			
		||||
  // is not a text input, so users can move in the 3D scene with
 | 
			
		||||
  // middle mouse drag with a text input focused without pasting.
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const handlePaste = (e: ClipboardEvent) => {
 | 
			
		||||
      const isHtmlElement = e.target && e.target instanceof HTMLElement
 | 
			
		||||
      const isEditable =
 | 
			
		||||
        (isHtmlElement && !('explicitOriginalTarget' in e)) ||
 | 
			
		||||
        ('explicitOriginalTarget' in e &&
 | 
			
		||||
          ((e.explicitOriginalTarget as HTMLElement).contentEditable ===
 | 
			
		||||
            'true' ||
 | 
			
		||||
            ['INPUT', 'TEXTAREA'].some(
 | 
			
		||||
              (tagName) =>
 | 
			
		||||
                tagName === (e.explicitOriginalTarget as HTMLElement).tagName
 | 
			
		||||
            )))
 | 
			
		||||
      if (!isEditable) {
 | 
			
		||||
        e.preventDefault()
 | 
			
		||||
        e.stopPropagation()
 | 
			
		||||
        e.stopImmediatePropagation()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    globalThis?.window?.document?.addEventListener('paste', handlePaste, {
 | 
			
		||||
      capture: true,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    const IDLE_TIME_MS = 1000 * 60 * 2
 | 
			
		||||
    let timeoutIdIdleA: ReturnType<typeof setTimeout> | undefined = undefined
 | 
			
		||||
 | 
			
		||||
    const teardown = () => {
 | 
			
		||||
      // Already paused
 | 
			
		||||
      if (streamState === StreamState.Paused) return
 | 
			
		||||
 | 
			
		||||
      videoRef.current?.pause()
 | 
			
		||||
      setStreamState(StreamState.Paused)
 | 
			
		||||
      sceneInfra.modelingSend({ type: 'Cancel' })
 | 
			
		||||
      // Give video time to pause
 | 
			
		||||
      window.requestAnimationFrame(() => {
 | 
			
		||||
        engineCommandManager.tearDown({ idleMode: true })
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const onVisibilityChange = () => {
 | 
			
		||||
      if (globalThis.window.document.visibilityState === 'hidden') {
 | 
			
		||||
        clearTimeout(timeoutIdIdleA)
 | 
			
		||||
        timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
 | 
			
		||||
      } else if (!engineCommandManager.engineConnection?.isReady()) {
 | 
			
		||||
        clearTimeout(timeoutIdIdleA)
 | 
			
		||||
        setStreamState(StreamState.Resuming)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Teardown everything if we go hidden or reconnect
 | 
			
		||||
    if (IDLE) {
 | 
			
		||||
      globalThis?.window?.document?.addEventListener(
 | 
			
		||||
        'visibilitychange',
 | 
			
		||||
        onVisibilityChange
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
 | 
			
		||||
 | 
			
		||||
    const onAnyInput = () => {
 | 
			
		||||
      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) {
 | 
			
		||||
      globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
 | 
			
		||||
      globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
 | 
			
		||||
      globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
 | 
			
		||||
      globalThis?.window?.document?.addEventListener('scroll', onAnyInput)
 | 
			
		||||
      globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (IDLE) {
 | 
			
		||||
      timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a listener to execute code and play the stream
 | 
			
		||||
     * on initial stream setup.
 | 
			
		||||
     */
 | 
			
		||||
    engineCommandManager.addEventListener(
 | 
			
		||||
      EngineCommandManagerEvents.SceneReady,
 | 
			
		||||
      executeCodeAndPlayStream
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      engineCommandManager.removeEventListener(
 | 
			
		||||
        EngineCommandManagerEvents.SceneReady,
 | 
			
		||||
        executeCodeAndPlayStream
 | 
			
		||||
      )
 | 
			
		||||
      globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
 | 
			
		||||
        capture: true,
 | 
			
		||||
      })
 | 
			
		||||
      if (IDLE) {
 | 
			
		||||
        clearTimeout(timeoutIdIdleA)
 | 
			
		||||
        clearTimeout(timeoutIdIdleB)
 | 
			
		||||
 | 
			
		||||
        globalThis?.window?.document?.removeEventListener(
 | 
			
		||||
          'visibilitychange',
 | 
			
		||||
          onVisibilityChange
 | 
			
		||||
        )
 | 
			
		||||
        globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
 | 
			
		||||
        globalThis?.window?.document?.removeEventListener(
 | 
			
		||||
          'mousemove',
 | 
			
		||||
          onAnyInput
 | 
			
		||||
        )
 | 
			
		||||
        globalThis?.window?.document?.removeEventListener(
 | 
			
		||||
          'mousedown',
 | 
			
		||||
          onAnyInput
 | 
			
		||||
        )
 | 
			
		||||
        globalThis?.window?.document?.removeEventListener('scroll', onAnyInput)
 | 
			
		||||
        globalThis?.window?.document?.removeEventListener(
 | 
			
		||||
          'touchstart',
 | 
			
		||||
          onAnyInput
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [IDLE, streamState])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (
 | 
			
		||||
      typeof window === 'undefined' ||
 | 
			
		||||
      typeof RTCPeerConnection === 'undefined'
 | 
			
		||||
    )
 | 
			
		||||
      return
 | 
			
		||||
    if (!videoRef.current) return
 | 
			
		||||
    if (!mediaStream) return
 | 
			
		||||
 | 
			
		||||
    // The browser complains if we try to load a new stream without pausing first.
 | 
			
		||||
    // Do not immediately play the stream!
 | 
			
		||||
    // we instead use a setTimeout to play the stream in the next event loop
 | 
			
		||||
    try {
 | 
			
		||||
      videoRef.current.srcObject = mediaStream
 | 
			
		||||
      videoRef.current.pause()
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        videoRef.current?.play().catch((e) => {
 | 
			
		||||
          console.warn('Video playing was prevented', e, videoRef.current)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.warn('Attempted to pause stream while play was still loading', e)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    send({
 | 
			
		||||
      type: 'Set context',
 | 
			
		||||
      data: {
 | 
			
		||||
        videoElement: videoRef.current,
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    setIsLoading(false)
 | 
			
		||||
  }, [mediaStream])
 | 
			
		||||
 | 
			
		||||
  const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {
 | 
			
		||||
    // If we've got no stream or connection, don't do anything
 | 
			
		||||
    if (!isNetworkOkay) return
 | 
			
		||||
    if (!videoRef.current) return
 | 
			
		||||
    // If we're in sketch mode, don't send a engine-side select event
 | 
			
		||||
    if (state.matches('Sketch')) return
 | 
			
		||||
    // Only respect default plane selection if we're on a selection command argument
 | 
			
		||||
    if (
 | 
			
		||||
      state.matches({ idle: 'showPlanes' }) &&
 | 
			
		||||
      !(
 | 
			
		||||
        commandBarState.matches('Gathering arguments') &&
 | 
			
		||||
        commandBarState.context.currentArgument?.inputType === 'selection'
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
      return
 | 
			
		||||
    // If we're mousing up from a camera drag, don't send a select event
 | 
			
		||||
    if (sceneInfra.camControls.wasDragging === true) return
 | 
			
		||||
 | 
			
		||||
    if (btnName(e.nativeEvent).left) {
 | 
			
		||||
      // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | 
			
		||||
      sendSelectEventToEngine(e)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * On double-click of sketch entities we automatically enter sketch mode with the selected sketch,
 | 
			
		||||
   * allowing for quick editing of sketches. TODO: This should be moved to a more central place.
 | 
			
		||||
   */
 | 
			
		||||
  const enterSketchModeIfSelectingSketch: MouseEventHandler<HTMLDivElement> = (
 | 
			
		||||
    e
 | 
			
		||||
  ) => {
 | 
			
		||||
    if (
 | 
			
		||||
      !isNetworkOkay ||
 | 
			
		||||
      !videoRef.current ||
 | 
			
		||||
      state.matches('Sketch') ||
 | 
			
		||||
      state.matches({ idle: 'showPlanes' }) ||
 | 
			
		||||
      sceneInfra.camControls.wasDragging === true ||
 | 
			
		||||
      !btnName(e.nativeEvent).left
 | 
			
		||||
    ) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sendSelectEventToEngine(e)
 | 
			
		||||
      .then(({ entity_id }) => {
 | 
			
		||||
        if (!entity_id) {
 | 
			
		||||
          // No entity selected. This is benign
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        const path = getArtifactOfTypes(
 | 
			
		||||
          { key: entity_id, types: ['path', 'solid2d', 'segment', 'helix'] },
 | 
			
		||||
          kclManager.artifactGraph
 | 
			
		||||
        )
 | 
			
		||||
        if (err(path)) {
 | 
			
		||||
          return path
 | 
			
		||||
        }
 | 
			
		||||
        sceneInfra.modelingSend({ type: 'Enter sketch' })
 | 
			
		||||
      })
 | 
			
		||||
      .catch(reportRejection)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    // eslint-disable-next-line jsx-a11y/no-static-element-interactions
 | 
			
		||||
    <div
 | 
			
		||||
      ref={videoWrapperRef}
 | 
			
		||||
      className="absolute inset-0 z-0"
 | 
			
		||||
      id="stream"
 | 
			
		||||
      data-testid="stream"
 | 
			
		||||
      onClick={handleClick}
 | 
			
		||||
      onDoubleClick={enterSketchModeIfSelectingSketch}
 | 
			
		||||
      onContextMenu={(e) => e.preventDefault()}
 | 
			
		||||
      onContextMenuCapture={(e) => e.preventDefault()}
 | 
			
		||||
    >
 | 
			
		||||
      <video
 | 
			
		||||
        ref={videoRef}
 | 
			
		||||
        muted
 | 
			
		||||
        autoPlay
 | 
			
		||||
        controls={false}
 | 
			
		||||
        onPlay={() => setIsLoading(false)}
 | 
			
		||||
        className="w-full cursor-pointer h-full"
 | 
			
		||||
        disablePictureInPicture
 | 
			
		||||
        id="video-stream"
 | 
			
		||||
      />
 | 
			
		||||
      <ClientSideScene
 | 
			
		||||
        cameraControls={settings.modeling.mouseControls.current}
 | 
			
		||||
      />
 | 
			
		||||
      {(streamState === StreamState.Paused ||
 | 
			
		||||
        streamState === StreamState.Resuming) && (
 | 
			
		||||
        <div className="text-center absolute inset-0">
 | 
			
		||||
          <div
 | 
			
		||||
            className="flex flex-col items-center justify-center h-screen"
 | 
			
		||||
            data-testid="paused"
 | 
			
		||||
          >
 | 
			
		||||
            <div className="border-primary border p-2 rounded-sm">
 | 
			
		||||
              <svg
 | 
			
		||||
                width="8"
 | 
			
		||||
                height="12"
 | 
			
		||||
                viewBox="0 0 8 12"
 | 
			
		||||
                fill="none"
 | 
			
		||||
                xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
              >
 | 
			
		||||
                <path
 | 
			
		||||
                  fillRule="evenodd"
 | 
			
		||||
                  clipRule="evenodd"
 | 
			
		||||
                  d="M2 12V0H0V12H2ZM8 12V0H6V12H8Z"
 | 
			
		||||
                  fill="var(--primary)"
 | 
			
		||||
                />
 | 
			
		||||
              </svg>
 | 
			
		||||
            </div>
 | 
			
		||||
            <p className="text-base mt-2 text-primary bold">
 | 
			
		||||
              {streamState === StreamState.Paused && 'Paused'}
 | 
			
		||||
              {streamState === StreamState.Resuming && 'Resuming'}
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
      {(!isNetworkOkay || isLoading) && (
 | 
			
		||||
        <div className="text-center absolute inset-0">
 | 
			
		||||
          <Loading>
 | 
			
		||||
            {!isNetworkOkay && !isLoading ? (
 | 
			
		||||
              <span data-testid="loading-stream">Stream disconnected...</span>
 | 
			
		||||
            ) : (
 | 
			
		||||
              !isLoading && (
 | 
			
		||||
                <span data-testid="loading-stream">Loading stream...</span>
 | 
			
		||||
              )
 | 
			
		||||
            )}
 | 
			
		||||
          </Loading>
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
      <ViewControlContextMenu
 | 
			
		||||
        event="mouseup"
 | 
			
		||||
        guard={(e) =>
 | 
			
		||||
          sceneInfra.camControls.wasDragging === false &&
 | 
			
		||||
          btnName(e).right === true
 | 
			
		||||
        }
 | 
			
		||||
        menuTargetElement={videoWrapperRef}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@ -841,7 +841,9 @@ class EngineConnection extends EventTarget {
 | 
			
		||||
 | 
			
		||||
            // Bust the cache before anything
 | 
			
		||||
            ;(async () => {
 | 
			
		||||
              await clearSceneAndBustCache(kclManager.engineCommandManager)
 | 
			
		||||
              await rustContext.clearSceneAndBustCache(
 | 
			
		||||
                kclManager.engineCommandManager.settings
 | 
			
		||||
              )
 | 
			
		||||
            })().catch(reportRejection)
 | 
			
		||||
 | 
			
		||||
            this.dispatchEvent(
 | 
			
		||||
@ -1395,8 +1397,6 @@ export class EngineCommandManager extends EventTarget {
 | 
			
		||||
    height: 1337,
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  elVideo: HTMLVideoElement | null = null
 | 
			
		||||
 | 
			
		||||
  _commandLogCallBack: (command: CommandLog[]) => void = () => {}
 | 
			
		||||
 | 
			
		||||
  subscriptions: {
 | 
			
		||||
 | 
			
		||||
@ -649,12 +649,13 @@ export async function sendSelectEventToEngine(
 | 
			
		||||
  e: React.MouseEvent<HTMLDivElement, MouseEvent>
 | 
			
		||||
) {
 | 
			
		||||
  // No video stream to normalise against, return immediately
 | 
			
		||||
  if (!engineCommandManager.elVideo)
 | 
			
		||||
  const engineStreamState = engineStreamActor.getSnapshot().context
 | 
			
		||||
  if (!engineStreamState.videoRef.current)
 | 
			
		||||
    return Promise.reject('video element not ready')
 | 
			
		||||
 | 
			
		||||
  const { x, y } = getNormalisedCoordinates(
 | 
			
		||||
    e,
 | 
			
		||||
    engineCommandManager.elVideo,
 | 
			
		||||
    engineStreamState.videoRef.current,
 | 
			
		||||
    engineCommandManager.streamDimensions
 | 
			
		||||
  )
 | 
			
		||||
  const res = await engineCommandManager.sendSceneCommand({
 | 
			
		||||
 | 
			
		||||
@ -6,10 +6,11 @@ import { authMachine } from '@src/machines/authMachine'
 | 
			
		||||
import { ACTOR_IDS } from '@src/machines/machineConstants'
 | 
			
		||||
import { settingsMachine } from '@src/machines/settingsMachine'
 | 
			
		||||
 | 
			
		||||
const { AUTH, SETTINGS } = ACTOR_IDS
 | 
			
		||||
const { AUTH, SETTINGS, ENGINE_STREAM } = ACTOR_IDS
 | 
			
		||||
const appMachineActors = {
 | 
			
		||||
  [AUTH]: authMachine,
 | 
			
		||||
  [SETTINGS]: settingsMachine,
 | 
			
		||||
  [ENGINE_STREAM]: engineStreamMachine,
 | 
			
		||||
} as const
 | 
			
		||||
 | 
			
		||||
const appMachine = setup({
 | 
			
		||||
@ -29,6 +30,11 @@ const appMachine = setup({
 | 
			
		||||
      systemId: SETTINGS,
 | 
			
		||||
      input: createSettings(),
 | 
			
		||||
    }),
 | 
			
		||||
    spawnChild(ENGINE_STREAM, {
 | 
			
		||||
      id: ENGINE_STREAM,
 | 
			
		||||
      systemId: ENGINE_STREAM,
 | 
			
		||||
      input: engineStreamContextCreate(),
 | 
			
		||||
    }),
 | 
			
		||||
  ],
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -61,3 +67,7 @@ export const useSettings = () =>
 | 
			
		||||
    const { currentProject, ...settings } = state.context
 | 
			
		||||
    return settings
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
export const engineStreamActor = appActor.system.get(ENGINE_STREAM) as ActorRefFrom<
 | 
			
		||||
  typeof engineStreamMachine
 | 
			
		||||
>
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,7 @@
 | 
			
		||||
import { uuidv4 } from 'lib/utils'
 | 
			
		||||
import { makeDefaultPlanes, clearSceneAndBustCache } from 'lang/wasm'
 | 
			
		||||
import { MutableRefObject } from 'react'
 | 
			
		||||
import { setup, assign, fromPromise } from 'xstate'
 | 
			
		||||
import { createActorContext } from '@xstate/react'
 | 
			
		||||
import { kclManager, sceneInfra, engineCommandManager } from 'lib/singletons'
 | 
			
		||||
import { rustContext, kclManager, sceneInfra, engineCommandManager } from 'lib/singletons'
 | 
			
		||||
import { trap } from 'lib/trap'
 | 
			
		||||
import { Vector3, Vector4 } from 'three'
 | 
			
		||||
 | 
			
		||||
@ -19,7 +17,9 @@ export enum EngineStreamState {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum EngineStreamTransition {
 | 
			
		||||
  SetMediaStream = 'set-context',
 | 
			
		||||
  SetMediaStream = 'set-media-stream',
 | 
			
		||||
  SetPool = 'set-pool',
 | 
			
		||||
  SetAuthToken = 'set-auth-token',
 | 
			
		||||
  Play = 'play',
 | 
			
		||||
  Resume = 'resume',
 | 
			
		||||
  Pause = 'pause',
 | 
			
		||||
@ -35,6 +35,15 @@ export interface EngineStreamContext {
 | 
			
		||||
  zoomToFit: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const engineStreamContextCreate = (): EngineStreamContext => ({
 | 
			
		||||
  pool: null,
 | 
			
		||||
  authToken: undefined,
 | 
			
		||||
  mediaStream: null,
 | 
			
		||||
  videoRef:  { current: null },
 | 
			
		||||
  canvasRef: { current: null },
 | 
			
		||||
  zoomToFit: true,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export function getDimensions(streamWidth: number, streamHeight: number) {
 | 
			
		||||
  const factorOf = 4
 | 
			
		||||
  const maxResolution = 2160
 | 
			
		||||
@ -47,11 +56,11 @@ export function getDimensions(streamWidth: number, streamHeight: number) {
 | 
			
		||||
  return { width: quadWidth, height: quadHeight }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function holdOntoVideoFrameInCanvas(
 | 
			
		||||
export async function holdOntoVideoFrameInCanvas(
 | 
			
		||||
  video: HTMLVideoElement,
 | 
			
		||||
  canvas: HTMLCanvasElement
 | 
			
		||||
) {
 | 
			
		||||
  video.pause()
 | 
			
		||||
  await video.pause()
 | 
			
		||||
  canvas.width = video.videoWidth
 | 
			
		||||
  canvas.height = video.videoHeight
 | 
			
		||||
  canvas.style.width = video.videoWidth + 'px'
 | 
			
		||||
@ -64,7 +73,7 @@ export function holdOntoVideoFrameInCanvas(
 | 
			
		||||
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const engineStreamMachine = setup({
 | 
			
		||||
export const engineStreamMachine = setup({
 | 
			
		||||
  types: {
 | 
			
		||||
    context: {} as EngineStreamContext,
 | 
			
		||||
    input: {} as EngineStreamContext,
 | 
			
		||||
@ -88,7 +97,7 @@ const engineStreamMachine = setup({
 | 
			
		||||
        video.style.display = 'block'
 | 
			
		||||
        canvas.style.display = 'none'
 | 
			
		||||
 | 
			
		||||
        await sceneInfra.camControls.restoreCameraPosition()
 | 
			
		||||
        // await sceneInfra.camControls.restoreCameraPosition()
 | 
			
		||||
 | 
			
		||||
        video.srcObject = mediaStream
 | 
			
		||||
        await video.play()
 | 
			
		||||
@ -105,12 +114,12 @@ const engineStreamMachine = setup({
 | 
			
		||||
        const video = context.videoRef.current
 | 
			
		||||
        if (!video) return
 | 
			
		||||
 | 
			
		||||
        video.pause()
 | 
			
		||||
        await video.pause()
 | 
			
		||||
 | 
			
		||||
        const canvas = context.canvasRef.current
 | 
			
		||||
        if (!canvas) return
 | 
			
		||||
 | 
			
		||||
        holdOntoVideoFrameInCanvas(video, canvas)
 | 
			
		||||
        await holdOntoVideoFrameInCanvas(video, canvas)
 | 
			
		||||
 | 
			
		||||
        // Make sure we're on the next frame for no flickering between canvas
 | 
			
		||||
        // and the video elements.
 | 
			
		||||
@ -126,57 +135,6 @@ const engineStreamMachine = setup({
 | 
			
		||||
              context.mediaStream?.getVideoTracks()[0].stop()
 | 
			
		||||
              video.srcObject = null
 | 
			
		||||
 | 
			
		||||
              const reqDefaultCameraGetSettings =
 | 
			
		||||
                await engineCommandManager.sendSceneCommand({
 | 
			
		||||
                  // CameraControls subscribes to default_camera_get_settings response events
 | 
			
		||||
                  // firing this at connection ensure the camera's are synced initially
 | 
			
		||||
                  type: 'modeling_cmd_req',
 | 
			
		||||
                  cmd_id: uuidv4(),
 | 
			
		||||
                  cmd: {
 | 
			
		||||
                    type: 'default_camera_get_settings',
 | 
			
		||||
                  },
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
              if (
 | 
			
		||||
                reqDefaultCameraGetSettings === null ||
 | 
			
		||||
                !reqDefaultCameraGetSettings.success
 | 
			
		||||
              )
 | 
			
		||||
                return
 | 
			
		||||
              if (
 | 
			
		||||
                !('modeling_response' in reqDefaultCameraGetSettings.resp.data)
 | 
			
		||||
              )
 | 
			
		||||
                return
 | 
			
		||||
              if (
 | 
			
		||||
                reqDefaultCameraGetSettings.resp.data.modeling_response.type ===
 | 
			
		||||
                'empty'
 | 
			
		||||
              )
 | 
			
		||||
                return
 | 
			
		||||
              if (
 | 
			
		||||
                !(
 | 
			
		||||
                  'settings' in
 | 
			
		||||
                  reqDefaultCameraGetSettings.resp.data.modeling_response.data
 | 
			
		||||
                )
 | 
			
		||||
              )
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
              const center =
 | 
			
		||||
                reqDefaultCameraGetSettings.resp.data.modeling_response.data
 | 
			
		||||
                  .settings.center
 | 
			
		||||
              const pos =
 | 
			
		||||
                reqDefaultCameraGetSettings.resp.data.modeling_response.data
 | 
			
		||||
                  .settings.pos
 | 
			
		||||
 | 
			
		||||
              sceneInfra.camControls.old = {
 | 
			
		||||
                camera: sceneInfra.camControls.camera.clone(),
 | 
			
		||||
                target: new Vector3(center.x, center.y, center.z),
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              sceneInfra.camControls.old.camera.position.set(
 | 
			
		||||
                pos.x,
 | 
			
		||||
                pos.y,
 | 
			
		||||
                pos.z
 | 
			
		||||
              )
 | 
			
		||||
 | 
			
		||||
              engineCommandManager.tearDown({ idleMode: true })
 | 
			
		||||
            })()
 | 
			
		||||
        )
 | 
			
		||||
@ -215,7 +173,7 @@ const engineStreamMachine = setup({
 | 
			
		||||
 | 
			
		||||
        // If we don't pause there could be a really bad flicker
 | 
			
		||||
        // on reconfiguration (resize, for example)
 | 
			
		||||
        holdOntoVideoFrameInCanvas(video, canvas)
 | 
			
		||||
        await holdOntoVideoFrameInCanvas(video, canvas)
 | 
			
		||||
        canvas.style.display = 'block'
 | 
			
		||||
 | 
			
		||||
        window.requestAnimationFrame(() => {
 | 
			
		||||
@ -231,9 +189,6 @@ const engineStreamMachine = setup({
 | 
			
		||||
            height,
 | 
			
		||||
            token: context.authToken,
 | 
			
		||||
            settings: settingsNext,
 | 
			
		||||
            makeDefaultPlanes: () => {
 | 
			
		||||
              return makeDefaultPlanes(kclManager.engineCommandManager)
 | 
			
		||||
            },
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          event.modelingMachineActorSend({
 | 
			
		||||
@ -250,11 +205,24 @@ const engineStreamMachine = setup({
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
}).createMachine({
 | 
			
		||||
  context: (initial) => initial.input,
 | 
			
		||||
  initial: EngineStreamState.Off,
 | 
			
		||||
  context: (initial) => initial.input,
 | 
			
		||||
  states: {
 | 
			
		||||
    [EngineStreamState.Off]: {
 | 
			
		||||
      reenter: true,
 | 
			
		||||
      on: {
 | 
			
		||||
        [EngineStreamTransition.SetPool]: {
 | 
			
		||||
          target: EngineStreamState.Setup,
 | 
			
		||||
          actions: [
 | 
			
		||||
            assign({ pool: ({ context, event }) => event.data.pool }),
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
        [EngineStreamTransition.SetAuthToken]: {
 | 
			
		||||
          target: EngineStreamState.Setup,
 | 
			
		||||
          actions: [
 | 
			
		||||
            assign({ authToken: ({ context, event }) => event.data.authToken }),
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
        [EngineStreamTransition.StartOrReconfigureEngine]: {
 | 
			
		||||
          target: EngineStreamState.On,
 | 
			
		||||
        },
 | 
			
		||||
@ -267,6 +235,7 @@ const engineStreamMachine = setup({
 | 
			
		||||
        input: (args) => args,
 | 
			
		||||
      },
 | 
			
		||||
      on: {
 | 
			
		||||
        // Transition requested by engineConnection
 | 
			
		||||
        [EngineStreamTransition.SetMediaStream]: {
 | 
			
		||||
          target: EngineStreamState.On,
 | 
			
		||||
          actions: [
 | 
			
		||||
@ -335,5 +304,3 @@ const engineStreamMachine = setup({
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export default createActorContext(engineStreamMachine)
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
export const ACTOR_IDS = {
 | 
			
		||||
  AUTH: 'auth',
 | 
			
		||||
  SETTINGS: 'settings',
 | 
			
		||||
  ENGINE_STREAM: 'engine_stream',
 | 
			
		||||
} as const
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user