Fix ids for kurt so front end re-uses same ones on executions (#4780)
* updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * working test; Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * Update src/wasm-lib/tests/executor/main.rs Co-authored-by: Jonathan Tran <jonnytran@gmail.com> * Update src/wasm-lib/tests/executor/main.rs Co-authored-by: Jonathan Tran <jonnytran@gmail.com> * fix race condition * fix whoopsie * fix tsc * for some dumb ass reason the model executes twice on load Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Jonathan Tran <jonnytran@gmail.com> Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
This commit is contained in:
		@ -112,10 +112,10 @@ test.describe('Editor tests', () => {
 | 
			
		||||
    await u.openDebugPanel()
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.locator('[data-receive-command-type="scene_clear_all"]')
 | 
			
		||||
    ).toHaveCount(1)
 | 
			
		||||
    ).toHaveCount(2)
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.locator('[data-message-type="execution-done"]')
 | 
			
		||||
    ).toHaveCount(1)
 | 
			
		||||
    ).toHaveCount(2)
 | 
			
		||||
 | 
			
		||||
    // Add whitespace to the end of the code.
 | 
			
		||||
    await u.codeLocator.click()
 | 
			
		||||
@ -133,10 +133,10 @@ test.describe('Editor tests', () => {
 | 
			
		||||
    // Make sure we didn't clear the scene.
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.locator('[data-message-type="execution-done"]')
 | 
			
		||||
    ).toHaveCount(2)
 | 
			
		||||
    ).toHaveCount(3)
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.locator('[data-receive-command-type="scene_clear_all"]')
 | 
			
		||||
    ).toHaveCount(1)
 | 
			
		||||
    ).toHaveCount(2)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('if you click the format button it formats your code and executes so lints are still there', async ({
 | 
			
		||||
 | 
			
		||||
@ -311,8 +311,6 @@ export class KclManager {
 | 
			
		||||
    // Do not send send scene commands if the program was interrupted, go to clean up
 | 
			
		||||
    if (!isInterrupted) {
 | 
			
		||||
      this.addDiagnostics(await lintAst({ ast: ast }))
 | 
			
		||||
 | 
			
		||||
      sceneInfra.modelingSend({ type: 'code edit during sketch' })
 | 
			
		||||
      setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
 | 
			
		||||
 | 
			
		||||
      if (args.zoomToFit) {
 | 
			
		||||
@ -358,7 +356,13 @@ export class KclManager {
 | 
			
		||||
      this.lastSuccessfulProgramMemory = execState.memory
 | 
			
		||||
    }
 | 
			
		||||
    this.ast = { ...ast }
 | 
			
		||||
    // updateArtifactGraph relies on updated executeState/programMemory
 | 
			
		||||
    await this.engineCommandManager.updateArtifactGraph(this.ast)
 | 
			
		||||
    this._executeCallback()
 | 
			
		||||
    if (!isInterrupted) {
 | 
			
		||||
      sceneInfra.modelingSend({ type: 'code edit during sketch' })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.engineCommandManager.addCommandLog({
 | 
			
		||||
      type: 'execution-done',
 | 
			
		||||
      data: null,
 | 
			
		||||
 | 
			
		||||
@ -66,9 +66,7 @@ export async function executeAst({
 | 
			
		||||
      ? enginelessExecutor(ast, programMemoryOverride)
 | 
			
		||||
      : _executor(ast, engineCommandManager))
 | 
			
		||||
 | 
			
		||||
    await engineCommandManager.waitForAllCommands(
 | 
			
		||||
      programMemoryOverride !== undefined
 | 
			
		||||
    )
 | 
			
		||||
    await engineCommandManager.waitForAllCommands()
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      logs: [],
 | 
			
		||||
 | 
			
		||||
@ -247,7 +247,7 @@ extrude003 = extrude(-15, sketch003)`
 | 
			
		||||
      selectedSegmentSnippet,
 | 
			
		||||
      expectedExtrudeSnippet
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
  }, 5_000)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const runModifyAstCloneWithEdgeTreatmentAndTag = async (
 | 
			
		||||
@ -477,7 +477,7 @@ extrude001 = extrude(-15, sketch001)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
extrude001 = extrude(-15, sketch001)
 | 
			
		||||
  |> chamfer({ length: 5, tags: [seg01] }, %)`
 | 
			
		||||
  |> chamfer({ length = 5, tags = [seg01] }, %)`
 | 
			
		||||
        const segmentSnippets = ['line([-20, 0], %)']
 | 
			
		||||
        const expectedCode = `sketch001 = startSketchOn('XY')
 | 
			
		||||
  |> startProfileAt([-10, 10], %)
 | 
			
		||||
@ -487,8 +487,8 @@ extrude001 = extrude(-15, sketch001)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
extrude001 = extrude(-15, sketch001)
 | 
			
		||||
  |> chamfer({ length: 5, tags: [seg01] }, %)
 | 
			
		||||
  |> ${edgeTreatmentType}({ ${parameterName}: 3, tags: [seg02] }, %)`
 | 
			
		||||
  |> chamfer({ length = 5, tags = [seg01] }, %)
 | 
			
		||||
  |> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)`
 | 
			
		||||
 | 
			
		||||
        await runModifyAstCloneWithEdgeTreatmentAndTag(
 | 
			
		||||
          code,
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { defaultSourceRange, SourceRange } from 'lang/wasm'
 | 
			
		||||
import { defaultSourceRange, Program, SourceRange } from 'lang/wasm'
 | 
			
		||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
 | 
			
		||||
import { Models } from '@kittycad/lib'
 | 
			
		||||
import { exportSave } from 'lib/exportSave'
 | 
			
		||||
@ -2099,30 +2099,23 @@ export class EngineCommandManager extends EventTarget {
 | 
			
		||||
   * When an execution takes place we want to wait until we've got replies for all of the commands
 | 
			
		||||
   * When this is done when we build the artifact map synchronously.
 | 
			
		||||
   */
 | 
			
		||||
  async waitForAllCommands(useFakeExecutor = false) {
 | 
			
		||||
    await Promise.all(Object.values(this.pendingCommands).map((a) => a.promise))
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      // the ast is wrong without this one tick timeout.
 | 
			
		||||
      // an example is `Solids should be select and deletable` e2e test will fail
 | 
			
		||||
      // because the out of date ast messes with selections
 | 
			
		||||
      // TODO: race condition
 | 
			
		||||
      if (!this?.kclManager) return
 | 
			
		||||
      this.artifactGraph = createArtifactGraph({
 | 
			
		||||
        orderedCommands: this.orderedCommands,
 | 
			
		||||
        responseMap: this.responseMap,
 | 
			
		||||
        ast: this.kclManager.ast,
 | 
			
		||||
      })
 | 
			
		||||
      if (useFakeExecutor) {
 | 
			
		||||
        // mock executions don't produce an artifactGraph, so this will always be empty
 | 
			
		||||
        // skipping the below logic to wait for the next real execution
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      if (this.artifactGraph.size) {
 | 
			
		||||
        this.deferredArtifactEmptied(null)
 | 
			
		||||
      } else {
 | 
			
		||||
        this.deferredArtifactPopulated(null)
 | 
			
		||||
      }
 | 
			
		||||
  waitForAllCommands() {
 | 
			
		||||
    return Promise.all(
 | 
			
		||||
      Object.values(this.pendingCommands).map((a) => a.promise)
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
  updateArtifactGraph(ast: Program) {
 | 
			
		||||
    this.artifactGraph = createArtifactGraph({
 | 
			
		||||
      orderedCommands: this.orderedCommands,
 | 
			
		||||
      responseMap: this.responseMap,
 | 
			
		||||
      ast,
 | 
			
		||||
    })
 | 
			
		||||
    // TODO check if these still need to be deferred once e2e tests are working again.
 | 
			
		||||
    if (this.artifactGraph.size) {
 | 
			
		||||
      this.deferredArtifactEmptied(null)
 | 
			
		||||
    } else {
 | 
			
		||||
      this.deferredArtifactPopulated(null)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 | 
			
		||||
@ -1895,11 +1895,19 @@ impl ExecutorContext {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if cache_result.clear_scene && !self.is_mock() {
 | 
			
		||||
            // Pop the execution state, since we are starting fresh.
 | 
			
		||||
            let mut id_generator = exec_state.id_generator.clone();
 | 
			
		||||
            // We do not pop the ids, since we want to keep the same id generator.
 | 
			
		||||
            // This is for the front end to keep track of the ids.
 | 
			
		||||
            id_generator.next_id = 0;
 | 
			
		||||
            *exec_state = ExecState {
 | 
			
		||||
                id_generator,
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // We don't do this in mock mode since there is no engine connection
 | 
			
		||||
            // anyways and from the TS side we override memory and don't want to clear it.
 | 
			
		||||
            self.reset_scene(exec_state, Default::default()).await?;
 | 
			
		||||
            // Pop the execution state, since we are starting fresh.
 | 
			
		||||
            *exec_state = Default::default();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: Use the top-level file's path.
 | 
			
		||||
 | 
			
		||||
@ -69,7 +69,7 @@ async fn do_execute_and_snapshot(
 | 
			
		||||
    Ok((exec_state, img))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn new_context(
 | 
			
		||||
pub async fn new_context(
 | 
			
		||||
    units: UnitLength,
 | 
			
		||||
    with_auth: bool,
 | 
			
		||||
    project_directory: Option<PathBuf>,
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
mod cache;
 | 
			
		||||
 | 
			
		||||
use kcl_lib::{
 | 
			
		||||
    test_server::{execute_and_snapshot, execute_and_snapshot_no_auth},
 | 
			
		||||
    test_server::{execute_and_snapshot, execute_and_snapshot_no_auth, new_context},
 | 
			
		||||
    UnitLength,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2001,3 +2001,62 @@ async fn kcl_test_error_no_auth_websocket() {
 | 
			
		||||
        .to_string()
 | 
			
		||||
        .contains("Please send the following object over this websocket"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
async fn kcl_test_ids_stable_between_executions() {
 | 
			
		||||
    let code = r#"sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([61.74, 206.13], %)
 | 
			
		||||
  |> xLine(305.11, %, $seg01)
 | 
			
		||||
  |> yLine(-291.85, %)
 | 
			
		||||
  |> xLine(-segLen(seg01), %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
  |> extrude(40.14, %)
 | 
			
		||||
  |> shell({
 | 
			
		||||
    faces: [seg01],
 | 
			
		||||
    thickness: 3.14,
 | 
			
		||||
  }, %)
 | 
			
		||||
"#;
 | 
			
		||||
 | 
			
		||||
    let ctx = new_context(UnitLength::Mm, true, None).await.unwrap();
 | 
			
		||||
    let old_program = kcl_lib::Program::parse_no_errs(code).unwrap();
 | 
			
		||||
    // Execute the program.
 | 
			
		||||
    let mut exec_state = Default::default();
 | 
			
		||||
    let cache_info = kcl_lib::CacheInformation {
 | 
			
		||||
        old: None,
 | 
			
		||||
        new_ast: old_program.ast.clone(),
 | 
			
		||||
    };
 | 
			
		||||
    ctx.run(cache_info, &mut exec_state).await.unwrap();
 | 
			
		||||
 | 
			
		||||
    // Get the id_generator from the first execution.
 | 
			
		||||
    let id_generator = exec_state.id_generator.clone();
 | 
			
		||||
 | 
			
		||||
    let code = r#"sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([62.74, 206.13], %)
 | 
			
		||||
  |> xLine(305.11, %, $seg01)
 | 
			
		||||
  |> yLine(-291.85, %)
 | 
			
		||||
  |> xLine(-segLen(seg01), %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
  |> extrude(40.14, %)
 | 
			
		||||
  |> shell({
 | 
			
		||||
    faces: [seg01],
 | 
			
		||||
    thickness: 3.14,
 | 
			
		||||
  }, %)
 | 
			
		||||
"#;
 | 
			
		||||
 | 
			
		||||
    // Execute a slightly different program again.
 | 
			
		||||
    let program = kcl_lib::Program::parse_no_errs(code).unwrap();
 | 
			
		||||
    let cache_info = kcl_lib::CacheInformation {
 | 
			
		||||
        old: Some(kcl_lib::OldAstState {
 | 
			
		||||
            ast: old_program.ast.clone(),
 | 
			
		||||
            exec_state: exec_state.clone(),
 | 
			
		||||
            settings: ctx.settings.clone(),
 | 
			
		||||
        }),
 | 
			
		||||
        new_ast: program.ast.clone(),
 | 
			
		||||
    };
 | 
			
		||||
    // Execute the program.
 | 
			
		||||
    ctx.run(cache_info, &mut exec_state).await.unwrap();
 | 
			
		||||
 | 
			
		||||
    assert_eq!(id_generator, exec_state.id_generator);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user