ArtifactGraph reThink (PART 3) (#3140)
* adjust engine connection to opt out of webRTC connection * refactor start and test setup * add env to unit test * spell config update * fix beforeAll order bug * initial integration of new artifact map with tests passing * remove old artifact map and clean up * graph artifact map * have graph commited * have graph commited * remove bad file * install playwright * fmt * commit permissions * typo * flesh out tests more * Look at this (photo)Graph *in the voice of Nickelback* * multi highlight * redo image logic * add in solid 2d data into artifactMap * fix snapshots * stabiles graph images * Look at this (photo)Graph *in the voice of Nickelback* * tweak tests * rename blend to edgeCut * Look at this (photo)Graph *in the voice of Nickelback* * fix playw tests * start of artifact map rename to graph * rename file * rename test * rename clearup * comments * docs * docs proof read * few tweaks here and there * typos * delete get parent logic * nit, combine if statements * remove unused param * fix silly test bug * rename surfId to sufaceId * rename types * update comments * add comment * add extra check * Look at this (photo)Graph *in the voice of Nickelback* * pull out merge artifact function * update comments * fix test * fmt --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -6,12 +6,12 @@ import { deferExecution, uuidv4 } from 'lib/utils'
|
||||
import { Themes, getThemeColorForEngine, getOppositeTheme } from 'lib/theme'
|
||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
import {
|
||||
ArtifactMap,
|
||||
ArtifactGraph,
|
||||
EngineCommand,
|
||||
OrderedCommand,
|
||||
ResponseMap,
|
||||
createArtifactMap,
|
||||
} from 'lang/std/artifactMap'
|
||||
createArtifactGraph,
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
// TODO(paultag): This ought to be tweakable.
|
||||
@ -286,8 +286,6 @@ class EngineConnection extends EventTarget {
|
||||
)
|
||||
}
|
||||
|
||||
private failedConnTimeout: IsomorphicTimeout | null
|
||||
|
||||
readonly url: string
|
||||
private readonly token?: string
|
||||
|
||||
@ -312,7 +310,6 @@ class EngineConnection extends EventTarget {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
this.url = url
|
||||
this.token = token
|
||||
this.failedConnTimeout = null
|
||||
|
||||
this.pingPongSpan = { ping: undefined, pong: undefined }
|
||||
|
||||
@ -451,9 +448,11 @@ class EngineConnection extends EventTarget {
|
||||
}
|
||||
|
||||
const createPeerConnection = () => {
|
||||
this.pc = new RTCPeerConnection({
|
||||
bundlePolicy: 'max-bundle',
|
||||
})
|
||||
if (!this.engineCommandManager.disableWebRTC) {
|
||||
this.pc = new RTCPeerConnection({
|
||||
bundlePolicy: 'max-bundle',
|
||||
})
|
||||
}
|
||||
|
||||
// Other parts of the application expect pc to be initialized when firing.
|
||||
this.dispatchEvent(
|
||||
@ -465,7 +464,7 @@ class EngineConnection extends EventTarget {
|
||||
// Data channels MUST BE specified before SDP offers because requesting
|
||||
// them affects what our needs are!
|
||||
const DATACHANNEL_NAME_UMC = 'unreliable_modeling_cmds'
|
||||
this.pc.createDataChannel(DATACHANNEL_NAME_UMC)
|
||||
this.pc?.createDataChannel?.(DATACHANNEL_NAME_UMC)
|
||||
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
@ -498,7 +497,7 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
})
|
||||
}
|
||||
this.pc.addEventListener('icecandidate', this.onIceCandidate)
|
||||
this.pc?.addEventListener?.('icecandidate', this.onIceCandidate)
|
||||
|
||||
this.onIceCandidateError = (_event: Event) => {
|
||||
const event = _event as RTCPeerConnectionIceErrorEvent
|
||||
@ -506,7 +505,7 @@ class EngineConnection extends EventTarget {
|
||||
`ICE candidate returned an error: ${event.errorCode}: ${event.errorText} for ${event.url}`
|
||||
)
|
||||
}
|
||||
this.pc.addEventListener('icecandidateerror', this.onIceCandidateError)
|
||||
this.pc?.addEventListener?.('icecandidateerror', this.onIceCandidateError)
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionstatechange_event
|
||||
// Event type: generic Event type...
|
||||
@ -540,7 +539,7 @@ class EngineConnection extends EventTarget {
|
||||
break
|
||||
}
|
||||
}
|
||||
this.pc.addEventListener(
|
||||
this.pc?.addEventListener?.(
|
||||
'connectionstatechange',
|
||||
this.onConnectionStateChange
|
||||
)
|
||||
@ -630,7 +629,7 @@ class EngineConnection extends EventTarget {
|
||||
|
||||
this.mediaStream = mediaStream
|
||||
}
|
||||
this.pc.addEventListener('track', this.onTrack)
|
||||
this.pc?.addEventListener?.('track', this.onTrack)
|
||||
|
||||
this.onDataChannel = (event) => {
|
||||
this.unreliableDataChannel = event.channel
|
||||
@ -721,7 +720,7 @@ class EngineConnection extends EventTarget {
|
||||
this.onDataChannelMessage
|
||||
)
|
||||
}
|
||||
this.pc.addEventListener('datachannel', this.onDataChannel)
|
||||
this.pc?.addEventListener?.('datachannel', this.onDataChannel)
|
||||
}
|
||||
|
||||
const createWebSocketConnection = () => {
|
||||
@ -756,6 +755,11 @@ class EngineConnection extends EventTarget {
|
||||
// Send an initial ping
|
||||
this.send({ type: 'ping' })
|
||||
this.pingPongSpan.ping = new Date()
|
||||
if (this.engineCommandManager.disableWebRTC) {
|
||||
this.engineCommandManager
|
||||
.initPlanes()
|
||||
.then(() => this.engineCommandManager.resolveReady())
|
||||
}
|
||||
}
|
||||
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
||||
|
||||
@ -803,7 +807,7 @@ class EngineConnection extends EventTarget {
|
||||
.join('\n')
|
||||
if (message.request_id) {
|
||||
const artifactThatFailed =
|
||||
this.engineCommandManager.artifactMap[message.request_id]
|
||||
this.engineCommandManager.artifactGraph.get(message.request_id)
|
||||
console.error(
|
||||
`Error in response to request ${message.request_id}:\n${errorsString}
|
||||
failed cmd type was ${artifactThatFailed?.type}`
|
||||
@ -1089,8 +1093,10 @@ export enum EngineCommandManagerEvents {
|
||||
* of those commands. It also sets up and tears down the connection to the Engine
|
||||
* through the {@link EngineConnection} class.
|
||||
*
|
||||
* It also maintains an {@link artifactMap} that keeps track of the state of each
|
||||
* command, and the artifacts that have been generated by those commands.
|
||||
* As commands are send their state is tracked in {@link pendingCommands} and clear as soon as we receive a response.
|
||||
*
|
||||
* Also all commands that are sent are kept track of in {@link orderedCommands} and their responses are kept in {@link responseMap}
|
||||
* Both of these data structures are used to process the {@link artifactGraph}.
|
||||
*/
|
||||
|
||||
interface PendingMessage {
|
||||
@ -1103,17 +1109,10 @@ interface PendingMessage {
|
||||
}
|
||||
export class EngineCommandManager extends EventTarget {
|
||||
/**
|
||||
* The artifactMap is a client-side representation of the commands that have been sent
|
||||
* to the server-side geometry engine, and the state of their resulting artifacts.
|
||||
*
|
||||
* It is used to keep track of the state of each command, which can fail, succeed, or be
|
||||
* pending.
|
||||
*
|
||||
* It is also used to keep track of our client's understanding of what is in the engine scene
|
||||
* so that we can map to and from KCL code. Each artifact maintains a source range to the part
|
||||
* of the KCL code that generated it.
|
||||
* The artifactGraph is a client-side representation of the commands that have been sent
|
||||
* see: src/lang/std/artifactGraph-README.md for a full explanation.
|
||||
*/
|
||||
artifactMap: ArtifactMap = {}
|
||||
artifactGraph: ArtifactGraph = new Map()
|
||||
/**
|
||||
* The pendingCommands object is a map of the commands that have been sent to the engine that are still waiting on a reply
|
||||
*/
|
||||
@ -1122,21 +1121,14 @@ export class EngineCommandManager extends EventTarget {
|
||||
} = {}
|
||||
/**
|
||||
* The orderedCommands array of all the the commands sent to the engine, un-folded from batches, and made into one long
|
||||
* list of the individual commands, this is used to process all the commands into the artifactMap
|
||||
* list of the individual commands, this is used to process all the commands into the artifactGraph
|
||||
*/
|
||||
orderedCommands: Array<OrderedCommand> = []
|
||||
/**
|
||||
* A map of the responses to the @this.orderedCommands, when processing the commands into the artifactMap, this response map allow
|
||||
* A map of the responses to the {@link orderedCommands}, when processing the commands into the artifactGraph, this response map allow
|
||||
* us to look up the response by command id
|
||||
*/
|
||||
responseMap: ResponseMap = {}
|
||||
/**
|
||||
* The client-side representation of the scene command artifacts that have been sent to the server;
|
||||
* that is, the *non-modeling* commands and corresponding artifacts.
|
||||
*
|
||||
* For modeling commands, see {@link artifactMap}.
|
||||
*/
|
||||
sceneCommandArtifacts: ArtifactMap = {}
|
||||
/**
|
||||
* A counter that is incremented with each command sent over the *unreliable* channel to the engine.
|
||||
* This is compared to the latest received {@link inSequence} number to determine if we should ignore
|
||||
@ -1158,7 +1150,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
reject: (reason: any) => void
|
||||
}
|
||||
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
||||
private resolveReady = () => {}
|
||||
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.
|
||||
*
|
||||
@ -1205,11 +1197,12 @@ export class EngineCommandManager extends EventTarget {
|
||||
private onEngineConnectionNewTrack = ({
|
||||
detail,
|
||||
}: CustomEvent<NewTrackArgs>) => {}
|
||||
disableWebRTC = false
|
||||
modelingSend: ReturnType<typeof useModelingContext>['send'] =
|
||||
(() => {}) as any
|
||||
|
||||
start({
|
||||
restart,
|
||||
disableWebRTC = false,
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
width,
|
||||
@ -1225,7 +1218,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
showScaleGrid: false,
|
||||
},
|
||||
}: {
|
||||
restart?: boolean
|
||||
disableWebRTC?: boolean
|
||||
setMediaStream: (stream: MediaStream) => void
|
||||
setIsStreamReady: (isStreamReady: boolean) => void
|
||||
width: number
|
||||
@ -1242,6 +1235,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
}
|
||||
}) {
|
||||
this.makeDefaultPlanes = makeDefaultPlanes
|
||||
this.disableWebRTC = disableWebRTC
|
||||
this.modifyGrid = modifyGrid
|
||||
if (width === 0 || height === 0) {
|
||||
return
|
||||
@ -1720,15 +1714,11 @@ export class EngineCommandManager extends EventTarget {
|
||||
if (this.engineConnection === undefined) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
if (!this.engineConnection?.isReady()) {
|
||||
if (!this.engineConnection?.isReady() && !this.disableWebRTC)
|
||||
return Promise.resolve()
|
||||
}
|
||||
if (id === undefined) {
|
||||
return Promise.reject(new Error('id is undefined'))
|
||||
}
|
||||
if (rangeStr === undefined) {
|
||||
if (id === undefined) return Promise.reject(new Error('id is undefined'))
|
||||
if (rangeStr === undefined)
|
||||
return Promise.reject(new Error('rangeStr is undefined'))
|
||||
}
|
||||
if (commandStr === undefined) {
|
||||
return Promise.reject(new Error('commandStr is undefined'))
|
||||
}
|
||||
@ -1800,18 +1790,18 @@ export class EngineCommandManager extends EventTarget {
|
||||
*/
|
||||
async waitForAllCommands() {
|
||||
await Promise.all(Object.values(this.pendingCommands).map((a) => a.promise))
|
||||
this.artifactMap = createArtifactMap({
|
||||
this.artifactGraph = createArtifactGraph({
|
||||
orderedCommands: this.orderedCommands,
|
||||
responseMap: this.responseMap,
|
||||
ast: this.getAst(),
|
||||
})
|
||||
if (Object.values(this.artifactMap).length) {
|
||||
if (this.artifactGraph.size) {
|
||||
this.deferredArtifactEmptied(null)
|
||||
} else {
|
||||
this.deferredArtifactPopulated(null)
|
||||
}
|
||||
}
|
||||
private async initPlanes() {
|
||||
async initPlanes() {
|
||||
if (this.planesInitialized()) return
|
||||
const planes = await this.makeDefaultPlanes()
|
||||
this.defaultPlanes = planes
|
||||
@ -1851,7 +1841,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
range: SourceRange,
|
||||
commandTypeToTarget: string
|
||||
): string | undefined {
|
||||
const values = Object.entries(this.artifactMap)
|
||||
const values = Object.entries(this.artifactGraph)
|
||||
for (const [id, data] of values) {
|
||||
// // Our range selection seems to just select the cursor position, so either
|
||||
// // of these can be right...
|
||||
|
Reference in New Issue
Block a user