Change artifact IDs to be stable across KCL executions (#4101)

* Add ID generator to ExecState

* Change default plane IDs to be hardcoded

* Fix lint warning

* Add exposing ID generator as output of executor

* Change to use generated definition of ExecState in TS

* Fix IdGenerator to use camel case in TS

* Fix TS type errors

* Add exposing id_generator parameter

* Add using the previously generated ID generator

* wip: Add display of feature tree in debug pane

* Remove artifact graph augmentation

* Change default planes to use id generator instead of hardcoded UUIDs

* Fix to reuse previously generated IDs

* Add e2e test

* Change feature tree to be collapsed by default

* Remove debug prints

* Fix unit test to use execState

* Fix type to be more general

* Remove outdated comment

* Update derive-docs output

* Fix object display component to be more general

* Remove unused ArtifactId type

* Fix test to be less brittle

* Remove codeRef and pathToNode from display

* Fix to remove test.only

Co-authored-by: Frank Noirot <frank@zoo.dev>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* Move plane conversion code to be next to type

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)"

This reverts commit 3455cc951b.

* Rename file

* Rename components and add doc comments

* Revive the collapse button

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* Confirm

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* Confirm

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* Confirm

---------

Co-authored-by: Frank Noirot <frank@zoo.dev>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Jonathan Tran
2024-10-09 19:38:40 -04:00
committed by GitHub
parent e525b319d0
commit 0fb5ff7f10
66 changed files with 961 additions and 400 deletions

View File

@ -37,6 +37,11 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { DeepPartial } from 'lib/types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
import { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
import { ExecState as RawExecState } from '../wasm-lib/kcl/bindings/ExecState'
import { ProgramMemory as RawProgramMemory } from '../wasm-lib/kcl/bindings/ProgramMemory'
import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
@ -136,29 +141,46 @@ export const parse = (code: string | Error): Program | Error => {
export type PathToNode = [string | number, string][]
interface Memory {
[key: string]: KclValue
export interface ExecState {
memory: ProgramMemory
idGenerator: IdGenerator
}
type EnvironmentRef = number
/**
* Create an empty ExecState. This is useful on init to prevent needing an
* Option.
*/
export function emptyExecState(): ExecState {
return {
memory: ProgramMemory.empty(),
idGenerator: defaultIdGenerator(),
}
}
function execStateFromRaw(raw: RawExecState): ExecState {
return {
memory: ProgramMemory.fromRaw(raw.memory),
idGenerator: raw.idGenerator,
}
}
export function defaultIdGenerator(): IdGenerator {
return {
nextId: 0,
ids: [],
}
}
interface Memory {
[key: string]: KclValue | undefined
}
const ROOT_ENVIRONMENT_REF: EnvironmentRef = 0
interface Environment {
bindings: Memory
parent: EnvironmentRef | null
}
function emptyEnvironment(): Environment {
return { bindings: {}, parent: null }
}
interface RawProgramMemory {
environments: Environment[]
currentEnv: EnvironmentRef
return: KclValue | null
}
/**
* This duplicates logic in Rust. The hope is to keep ProgramMemory internals
* isolated from the rest of the TypeScript code so that we can move it to Rust
@ -217,7 +239,7 @@ export class ProgramMemory {
while (true) {
const env = this.environments[envRef]
if (env.bindings.hasOwnProperty(name)) {
return env.bindings[name]
return env.bindings[name] ?? null
}
if (!env.parent) {
break
@ -260,6 +282,7 @@ export class ProgramMemory {
}
for (const [name, value] of Object.entries(env.bindings)) {
if (value === undefined) continue
// Check the predicate.
if (!predicate(value)) {
continue
@ -293,6 +316,7 @@ export class ProgramMemory {
while (true) {
const env = this.environments[envRef]
for (const [name, value] of Object.entries(env.bindings)) {
if (value === undefined) continue
// Don't include shadowed variables.
if (!map.has(name)) {
map.set(name, value)
@ -356,9 +380,10 @@ export function sketchFromKclValue(
export const executor = async (
node: Program,
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator(),
engineCommandManager: EngineCommandManager,
isMock: boolean = false
): Promise<ProgramMemory> => {
): Promise<ExecState> => {
if (err(programMemory)) return Promise.reject(programMemory)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@ -366,6 +391,7 @@ export const executor = async (
const _programMemory = await _executor(
node,
programMemory,
idGenerator,
engineCommandManager,
isMock
)
@ -378,9 +404,10 @@ export const executor = async (
export const _executor = async (
node: Program,
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator(),
engineCommandManager: EngineCommandManager,
isMock: boolean
): Promise<ProgramMemory> => {
): Promise<ExecState> => {
if (err(programMemory)) return Promise.reject(programMemory)
try {
@ -392,15 +419,16 @@ export const _executor = async (
baseUnit =
(await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
}
const memory: RawProgramMemory = await execute_wasm(
const execState: RawExecState = await execute_wasm(
JSON.stringify(node),
JSON.stringify(programMemory.toRaw()),
JSON.stringify(idGenerator),
baseUnit,
engineCommandManager,
fileSystemManager,
isMock
)
return ProgramMemory.fromRaw(memory)
return execStateFromRaw(execState)
} catch (e: any) {
console.log(e)
const parsed: RustKclError = JSON.parse(e.toString())