kopia lustrzana https://github.com/Tldraw/Tldraw
alex/auto-undo-redo: add bail-out
rodzic
2751a056f2
commit
3448e53a64
|
@ -88,25 +88,13 @@ export class SideEffectManager<
|
|||
return next
|
||||
}
|
||||
|
||||
let updateDepth = 0
|
||||
|
||||
editor.store.onAfterChange = (prev, next, source) => {
|
||||
updateDepth++
|
||||
|
||||
if (updateDepth > 1000) {
|
||||
console.error('[CleanupManager.onAfterChange] Maximum update depth exceeded, bailing out.')
|
||||
} else {
|
||||
const handlers = this._afterChangeHandlers[
|
||||
next.typeName
|
||||
] as TLAfterChangeHandler<TLRecord>[]
|
||||
if (handlers) {
|
||||
for (const handler of handlers) {
|
||||
handler(prev, next, source)
|
||||
}
|
||||
const handlers = this._afterChangeHandlers[next.typeName] as TLAfterChangeHandler<TLRecord>[]
|
||||
if (handlers) {
|
||||
for (const handler of handlers) {
|
||||
handler(prev, next, source)
|
||||
}
|
||||
}
|
||||
|
||||
updateDepth--
|
||||
}
|
||||
|
||||
editor.store.onBeforeDelete = (record, source) => {
|
||||
|
|
|
@ -825,38 +825,53 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
|
|||
this.pendingAfterEvents.set(id, { before, after, source })
|
||||
}
|
||||
}
|
||||
private flushAtomicCallbacks() {
|
||||
let updateDepth = 0
|
||||
while (this.pendingAfterEvents) {
|
||||
const events = this.pendingAfterEvents
|
||||
this.pendingAfterEvents = null
|
||||
|
||||
if (!this._runCallbacks) continue
|
||||
|
||||
updateDepth++
|
||||
if (updateDepth > 100) {
|
||||
throw new Error('Maximum store update depth exceeded, bailing out')
|
||||
}
|
||||
|
||||
for (const { before, after, source } of events.values()) {
|
||||
if (before && after) {
|
||||
this.onAfterChange?.(before, after, source)
|
||||
} else if (before && !after) {
|
||||
this.onAfterDelete?.(before, source)
|
||||
} else if (!before && after) {
|
||||
this.onAfterCreate?.(after, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private _isInAtomicOp = false
|
||||
/** @internal */
|
||||
atomic<T>(fn: () => T, runCallbacks = true): T {
|
||||
return transact(() => {
|
||||
if (this.pendingAfterEvents) return fn()
|
||||
if (this._isInAtomicOp) {
|
||||
if (!this.pendingAfterEvents) this.pendingAfterEvents = new Map()
|
||||
return fn()
|
||||
}
|
||||
|
||||
this.pendingAfterEvents = new Map()
|
||||
const prevRunCallbacks = this._runCallbacks
|
||||
this._runCallbacks = runCallbacks ?? prevRunCallbacks
|
||||
this._isInAtomicOp = true
|
||||
try {
|
||||
const result = fn()
|
||||
|
||||
while (this.pendingAfterEvents) {
|
||||
const events = this.pendingAfterEvents
|
||||
this.pendingAfterEvents = null
|
||||
|
||||
if (!runCallbacks) continue
|
||||
|
||||
for (const { before, after, source } of events.values()) {
|
||||
if (before && after) {
|
||||
this.onAfterChange?.(before, after, source)
|
||||
} else if (before && !after) {
|
||||
this.onAfterDelete?.(before, source)
|
||||
} else if (!before && after) {
|
||||
this.onAfterCreate?.(after, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.flushAtomicCallbacks()
|
||||
|
||||
return result
|
||||
} finally {
|
||||
this.pendingAfterEvents = null
|
||||
this._runCallbacks = prevRunCallbacks
|
||||
this._isInAtomicOp = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1157,4 +1157,29 @@ describe('after callbacks', () => {
|
|||
})
|
||||
expect(callbacks).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('bails out if too many callbacks are fired', () => {
|
||||
let limit = 10
|
||||
store.onAfterCreate = (record) => {
|
||||
if (record.typeName === 'book' && record.numPages < limit) {
|
||||
store.put([{ ...record, numPages: record.numPages + 1 }])
|
||||
}
|
||||
}
|
||||
store.onAfterChange = (from, to) => {
|
||||
if (to.typeName === 'book' && to.numPages < limit) {
|
||||
store.put([{ ...to, numPages: to.numPages + 1 }])
|
||||
}
|
||||
}
|
||||
|
||||
// this should be fine:
|
||||
store.put([Book.create({ title: 'The Hobbit', id: bookId, author: authorId, numPages: 0 })])
|
||||
expect(store.get(bookId)!.numPages).toBe(limit)
|
||||
|
||||
// if we increase the limit thought, it should crash:
|
||||
limit = 10000
|
||||
store.clear()
|
||||
expect(() => {
|
||||
store.put([Book.create({ title: 'The Hobbit', id: bookId, author: authorId, numPages: 0 })])
|
||||
}).toThrowErrorMatchingInlineSnapshot(`"Maximum store update depth exceeded, bailing out"`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -224,9 +224,9 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
isPrecise: boolean;
|
||||
}>;
|
||||
point: ObjectValidator< {
|
||||
type: "point";
|
||||
x: number;
|
||||
y: number;
|
||||
type: "point";
|
||||
}>;
|
||||
}, never>;
|
||||
end: UnionValidator<"type", {
|
||||
|
@ -238,9 +238,9 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
isPrecise: boolean;
|
||||
}>;
|
||||
point: ObjectValidator< {
|
||||
type: "point";
|
||||
x: number;
|
||||
y: number;
|
||||
type: "point";
|
||||
}>;
|
||||
}, never>;
|
||||
bend: Validator<number>;
|
||||
|
|
|
@ -1645,7 +1645,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<{\n type: \"point\";\n x: number;\n y: number;\n }>;\n }, never>;\n end: import(\"@tldraw/editor\")."
|
||||
"text": "<{\n x: number;\n y: number;\n type: \"point\";\n }>;\n }, never>;\n end: import(\"@tldraw/editor\")."
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -1690,7 +1690,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<{\n type: \"point\";\n x: number;\n y: number;\n }>;\n }, never>;\n bend: import(\"@tldraw/editor\")."
|
||||
"text": "<{\n x: number;\n y: number;\n type: \"point\";\n }>;\n }, never>;\n bend: import(\"@tldraw/editor\")."
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
|
|
@ -47,9 +47,9 @@ export const arrowShapeProps: {
|
|||
isPrecise: boolean;
|
||||
} & {}>;
|
||||
point: T.ObjectValidator<{
|
||||
type: "point";
|
||||
x: number;
|
||||
y: number;
|
||||
type: "point";
|
||||
} & {}>;
|
||||
}, never>;
|
||||
end: T.UnionValidator<"type", {
|
||||
|
@ -61,9 +61,9 @@ export const arrowShapeProps: {
|
|||
isPrecise: boolean;
|
||||
} & {}>;
|
||||
point: T.ObjectValidator<{
|
||||
type: "point";
|
||||
x: number;
|
||||
y: number;
|
||||
type: "point";
|
||||
} & {}>;
|
||||
}, never>;
|
||||
bend: T.Validator<number>;
|
||||
|
|
|
@ -364,7 +364,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<{\n type: \"point\";\n x: number;\n y: number;\n } & {}>;\n }, never>;\n end: "
|
||||
"text": "<{\n x: number;\n y: number;\n type: \"point\";\n } & {}>;\n }, never>;\n end: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
@ -409,7 +409,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<{\n type: \"point\";\n x: number;\n y: number;\n } & {}>;\n }, never>;\n bend: "
|
||||
"text": "<{\n x: number;\n y: number;\n type: \"point\";\n } & {}>;\n }, never>;\n bend: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
|
|
Ładowanie…
Reference in New Issue