Fix deletion-failure bug (#10888) (#10889)

Fix intermittent deletion failures (#10888)

- Fix bug in deletion logic (the `syncModule` itself was passed to a function that tried to commit it as an edit)
- Refactor APIs to avoid similar bugs (remove `direct` edit option)

# Important Notes
- `graphStore.getMutable` provides safer access to some of the functionality that was exposed by `direct` editing.
- `graphStore.transact` has been eliminated; it was redundant with `graphStore.batchEdits`.
This commit is contained in:
Kaz Wesley 2024-08-28 07:28:43 -07:00 committed by GitHub
parent bd5ab43cf6
commit 92995d191f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 28 additions and 36 deletions

View File

@ -24,7 +24,7 @@ const editedNodeInitialColors = new Map<NodeId, string | undefined>()
function setColor(color: string | undefined) { function setColor(color: string | undefined) {
currentColor.value = color currentColor.value = color
graphStore.transact(() => { graphStore.batchEdits(() => {
if (color) { if (color) {
for (const node of selection.selected) { for (const node of selection.selected) {
if (!editedNodeInitialColors.has(node)) if (!editedNodeInitialColors.has(node))

View File

@ -336,7 +336,7 @@ const graphBindingsHandler = graphBindings.handler({
selected, selected,
(id) => graphStore.db.nodeIdToNode.get(id)?.vis?.visible === true, (id) => graphStore.db.nodeIdToNode.get(id)?.vis?.visible === true,
) )
graphStore.transact(() => { graphStore.batchEdits(() => {
for (const nodeId of selected) { for (const nodeId of selected) {
graphStore.setNodeVisualization(nodeId, { visible: !allVisible }) graphStore.setNodeVisualization(nodeId, { visible: !allVisible })
} }

View File

@ -54,7 +54,7 @@ function edgeInteractionClick(graphNavigator: GraphNavigator) {
} }
const target = graph.mouseEditedEdge.target ?? selection?.hoveredPort const target = graph.mouseEditedEdge.target ?? selection?.hoveredPort
const targetNode = target && graph.getPortNodeId(target) const targetNode = target && graph.getPortNodeId(target)
graph.transact(() => { graph.batchEdits(() => {
if (source != null && sourceNode != targetNode) { if (source != null && sourceNode != targetNode) {
if (target == null) { if (target == null) {
if (graph.mouseEditedEdge?.disconnectedEdgeTarget != null) if (graph.mouseEditedEdge?.disconnectedEdgeTarget != null)

View File

@ -11,12 +11,7 @@ export function useAstDocumentation(graphStore: GraphStore, ast: ToValue<Ast | u
const astValue = toValue(ast) const astValue = toValue(ast)
if (!astValue) return if (!astValue) return
if (value.trimStart() !== '') { if (value.trimStart() !== '') {
graphStore.edit( graphStore.getMutable(astValue).getOrInitDocumentation().setDocumentationText(value)
(edit) =>
edit.getVersion(astValue).getOrInitDocumentation().setDocumentationText(value),
true,
true,
)
} else { } else {
// Remove the documentation node. // Remove the documentation node.
const documented = astValue.documentingAncestor() const documented = astValue.documentingAncestor()

View File

@ -300,23 +300,19 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC
} }
function deleteNodes(ids: Iterable<NodeId>) { function deleteNodes(ids: Iterable<NodeId>) {
edit( edit((edit) => {
(edit) => { for (const id of ids) {
for (const id of ids) { const node = db.nodeIdToNode.get(id)
const node = db.nodeIdToNode.get(id) if (!node) continue
if (!node) continue if (node.type !== 'component') continue
if (node.type !== 'component') continue const usages = db.getNodeUsages(id)
const usages = db.getNodeUsages(id) for (const usage of usages) updatePortValue(edit, usage, undefined)
for (const usage of usages) updatePortValue(edit, usage, undefined) const outerExpr = edit.getVersion(node.outerExpr)
const outerExpr = edit.getVersion(node.outerExpr) if (outerExpr) Ast.deleteFromParentBlock(outerExpr)
if (outerExpr) Ast.deleteFromParentBlock(outerExpr) nodeRects.delete(id)
nodeRects.delete(id) nodeHoverAnimations.delete(id)
nodeHoverAnimations.delete(id) }
} })
},
true,
true,
)
} }
function setNodeContent( function setNodeContent(
@ -346,10 +342,6 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC
}) })
} }
function transact(fn: () => void) {
syncModule.value!.transact(fn)
}
const undoManager = { const undoManager = {
undo() { undo() {
proj.module?.undoManager.undo() proj.module?.undoManager.undo()
@ -542,11 +534,9 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC
* *
* @param skipTreeRepair - If the edit is certain not to produce incorrect or non-canonical syntax, this may be set * @param skipTreeRepair - If the edit is certain not to produce incorrect or non-canonical syntax, this may be set
* to `true` for better performance. * to `true` for better performance.
* @param direct - Apply all changes directly to the synchronized module; they will be committed even if the callback
* exits by throwing an exception.
*/ */
function edit<T>(f: (edit: MutableModule) => T, skipTreeRepair?: boolean, direct?: boolean): T { function edit<T>(f: (edit: MutableModule) => T, skipTreeRepair?: boolean): T {
const edit = direct ? syncModule.value : syncModule.value?.edit() const edit = syncModule.value?.edit()
assert(edit != null) assert(edit != null)
let result let result
edit.transact(() => { edit.transact(() => {
@ -556,11 +546,18 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC
assert(root instanceof Ast.BodyBlock) assert(root instanceof Ast.BodyBlock)
Ast.repair(root, edit) Ast.repair(root, edit)
} }
if (!direct) syncModule.value!.applyEdit(edit) syncModule.value!.applyEdit(edit)
}) })
return result! return result!
} }
/** Obtain a version of the given `Ast` for direct mutation. The `ast` must exist in the current module.
* This can be more efficient than creating and committing an edit, but skips tree-repair and cannot be aborted.
*/
function getMutable<T extends Ast.Ast>(ast: T): Ast.Mutable<T> {
return syncModule.value!.getVersion(ast)
}
function batchEdits(f: () => void, origin: Origin = defaultLocalOrigin) { function batchEdits(f: () => void, origin: Origin = defaultLocalOrigin) {
assert(syncModule.value != null) assert(syncModule.value != null)
syncModule.value.transact(f, origin) syncModule.value.transact(f, origin)
@ -691,7 +688,6 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC
}) })
return proxyRefs({ return proxyRefs({
transact,
db: markRaw(db), db: markRaw(db),
mockExpressionUpdate, mockExpressionUpdate,
editedNodeInfo, editedNodeInfo,
@ -710,6 +706,7 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC
pickInCodeOrder, pickInCodeOrder,
ensureCorrectNodeOrder, ensureCorrectNodeOrder,
batchEdits, batchEdits,
getMutable,
overrideNodeColor, overrideNodeColor,
getNodeColorOverride, getNodeColorOverride,
setNodeContent, setNodeContent,