-
Notifications
You must be signed in to change notification settings - Fork 97
Refactor KclPlugin to not keep any state #9272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Refactor KclPlugin to not keep any state #9272
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| const executeKclDeferred = deferExecution( | ||
| async ({ newCode }: { newCode: string }) => { | ||
| void systemDeps.kclManager.writeToFile(newCode) | ||
| await systemDeps.kclManager.executeCode(newCode) | ||
| }, | ||
| changesDelay | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Race condition: writeToFile is not awaited before executeCode runs. The old code explicitly waited for the file write to complete via .then() before scheduling execution. Now execution can start while the file is still being written.
const executeKclDeferred = deferExecution(
async ({ newCode }: { newCode: string }) => {
await systemDeps.kclManager.writeToFile(newCode)
await systemDeps.kclManager.executeCode(newCode)
},
changesDelay
)| const executeKclDeferred = deferExecution( | |
| async ({ newCode }: { newCode: string }) => { | |
| void systemDeps.kclManager.writeToFile(newCode) | |
| await systemDeps.kclManager.executeCode(newCode) | |
| }, | |
| changesDelay | |
| ) | |
| const executeKclDeferred = deferExecution( | |
| async ({ newCode }: { newCode: string }) => { | |
| await systemDeps.kclManager.writeToFile(newCode) | |
| await systemDeps.kclManager.executeCode(newCode) | |
| }, | |
| changesDelay | |
| ) |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is a problem, writing to file at executing the code at the same time? Even if either fails we probably want to do the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's what I was thinking. I shouldn't change that when so many other critical things are being nudged though, so I reverted it anyway.
src/editor/plugins/lsp/kcl/index.ts
Outdated
|
|
||
| if (shouldExecute) { | ||
| const newCode = update.state.doc.toString() | ||
| executeKclDeferred({ newCode }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case newCode may not be the same as kclManager.code ?
src/lang/KclManager.ts
Outdated
| } | ||
| async executeCode(): Promise<void> { | ||
| const ast = await this.safeParse(this.code) | ||
| async executeCode(newCode = this.code): Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pattern of optional params that use a member property if not given can be error prone but here it's needed because of executeKclDeferred right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah I agree bad. It was my way to try to get new execution flow behavior (not relying on the duplicate kclManager.code state) to work without breaking all the call sites of kclManager.executeCode() scattered about, but it turns out this was the thing breaking that test! I've reverted it, thanks for highlighting it.
andrewvarga
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's so much nicer without the state in the plugin, less code as well, makes me wonder why we added that in the first place.
Great cleanup.
| // Gotcha: Code can be written into the CodeMirror editor but not propagated to kclManager.code | ||
| // because the update function has not run. We need to initialize the kclManager.code when lsp initializes | ||
| // because new code could have been written into the editor before the update callback is initialized. | ||
| // There appears to be limited ways to safely get the current doc content. This appears to be sync and safe. | ||
| const kclLspPlugin = this.client.plugins.find((plugin) => { | ||
| return plugin.client.name === 'kcl' | ||
| }) | ||
| if (kclLspPlugin) { | ||
| systemDeps.kclManager.code = view.state.doc.toString() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I revived this block of code as hackSetInitialCodeFromLsp, a "run once" extension that sets the code on kclManager initially. I believe we still need it until we get rid of React / tearing down the EditorView, although we have no automated tests that capture that.
| // If we're in sketchSolveMode, update Rust state with the latest AST | ||
| // This handles the case where the user directly edits in the CodeMirror editor | ||
| // these are short term hacks while in rapid development for sketch revamp | ||
| // should be clean up. | ||
| try { | ||
| const modelingState = kclManager.modelingState | ||
| const executeKclDeferred = deferExecution( | ||
| async ({ newCode }: { newCode: string }) => { | ||
| const modelingState = systemDeps.kclManager.modelingState | ||
| if (modelingState?.matches('sketchSolveMode')) { | ||
| await kclManager.executeCode() | ||
| const { sceneGraph, execOutcome } = await rustContext.hackSetProgram( | ||
| kclManager.ast, | ||
| await jsAppSettings() | ||
| ) | ||
|
|
||
| // Convert SceneGraph to SceneGraphDelta and send to sketch solve machine | ||
| const sceneGraphDelta: SceneGraphDelta = { | ||
| new_graph: sceneGraph, | ||
| new_objects: [], | ||
| invalidates_ids: false, | ||
| exec_outcome: execOutcome, | ||
| } | ||
|
|
||
| const kclSource: SourceDelta = { | ||
| text: kclManager.code, | ||
| } | ||
|
|
||
| // Send event to sketch solve machine via modeling machine | ||
| kclManager.sendModelingEvent({ | ||
| type: 'update sketch outcome', | ||
| data: { | ||
| kclSource, | ||
| sceneGraphDelta, | ||
| }, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had completely overlooked this new hack code for sketch solve mode while I was initially writing this PR last night! I've revived it as hackExecutionForSketchSolve.
| const executeKclDeferred = deferExecution( | ||
| async ({ newCode }: { newCode: string }) => { | ||
| void systemDeps.kclManager.writeToFile(newCode) | ||
| await systemDeps.kclManager.executeCode(newCode) | ||
| }, | ||
| changesDelay | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's what I was thinking. I shouldn't change that when so many other critical things are being nudged though, so I reverted it anyway.
| sceneGraphDelta, | ||
| }, | ||
| }) | ||
| await hackExecutionForSketchSolve(newCode, systemDeps.kclManager) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing code assignment in sketch solve mode. When in sketchSolveMode, the kclManager.code is never updated with newCode, causing stale code to persist in the manager.
In the old implementation, kclManager.code was set before execution (line 149 in old code). In the refactored version, code is only set in the else branch (line 73), but not in the sketch solve path.
Fix:
await hackExecutionForSketchSolve(newCode, systemDeps.kclManager)
systemDeps.kclManager.code = newCodeOr alternatively, set it inside hackExecutionForSketchSolve before the execution.
| await hackExecutionForSketchSolve(newCode, systemDeps.kclManager) | |
| await hackExecutionForSketchSolve(newCode, systemDeps.kclManager) | |
| systemDeps.kclManager.code = newCode |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
Towards #8885, broken out from #9190. Refactors the KCL LSP extension to not store any EditorView or other state. In addition, splits out concerns of highlighting engine-side entities and executing code. Users should experience no degradations from this refactor, although it is possible that this fixes some niche case bugs.
bfdf1a2 to
662c41f
Compare
662c41f to
a84f53b
Compare
|
This is tricky as shit!
So annoying |
Is the failure showing a real problem? just wanna make sure it's not a case where the test just needs to be tweaked. |
Towards #8885, broken out from #9190. Refactors the KCL LSP extension to not store any EditorView or other state. In addition, splits out concerns of highlighting engine-side entities and executing code. Users should experience no degradations from this refactor, although it is possible that this fixes some niche case bugs.