diff --git a/CHANGELOG.md b/CHANGELOG.md index 099dd1cb4a9..7e9a3454510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [next] - perf(): Rework constructors to avoid the extra perf cost of current setup [#9891](https://github.com/fabricjs/fabric.js/pull/9891) +- perf(ObjectGeometry): replace cache key string with array [#9887](https://github.com/fabricjs/fabric.js/pull/9887) - docs(): Improve JSDOCs for BlendImage [#9876](https://github.com/fabricjs/fabric.js/pull/9876) - fix(Group): Pass down the abort signal from group to objects [#9890](https://github.com/fabricjs/fabric.js/pull/9890) - fix(util): restore old composeMatrix code for performances improvement [#9851](https://github.com/fabricjs/fabric.js/pull/9851) diff --git a/src/benchmarks/transformMatrixKey.mjs b/src/benchmarks/transformMatrixKey.mjs new file mode 100644 index 00000000000..a3bae78774c --- /dev/null +++ b/src/benchmarks/transformMatrixKey.mjs @@ -0,0 +1,97 @@ +import { FabricObject, Group } from '../../dist/index.mjs'; + +// Swapping of calcCornerCoords in #9377 + +// OLD CODE FOR REFERENCE AND IMPLEMENTATION TEST + +class OldObject extends FabricObject { + transformMatrixKey(skipGroup = false) { + const sep = '_'; + let prefix = ''; + if (!skipGroup && this.group) { + prefix = this.group.transformMatrixKey(skipGroup) + sep; + } + return ( + prefix + + this.top + + sep + + this.left + + sep + + this.scaleX + + sep + + this.scaleY + + sep + + this.skewX + + sep + + this.skewY + + sep + + this.angle + + sep + + this.originX + + sep + + this.originY + + sep + + this.width + + sep + + this.height + + sep + + this.strokeWidth + + this.flipX + + this.flipY + ); + } +} + +class OldGroup extends Group { + transformMatrixKey(skipGroup = false) { + return OldObject.prototype.transformMatrixKey.call(this, skipGroup); + } +} + +// END OF OLD CODE + +const newComplexObject = new FabricObject({ width: 100, height: 100 }); +const newComplexGroup = new Group([newComplexObject]); +new Group([newComplexGroup]); + +const oldComplexObject = new OldObject({ width: 100, height: 100 }); +const oldComplexGroup = new OldGroup([oldComplexObject]); +new OldGroup([oldComplexGroup]); + +const benchmark = (callback) => { + const start = Date.now(); + callback(); + return Date.now() - start; +}; + +const complexNew = benchmark(() => { + for (let i = 0; i < 1_000_000; i++) { + newComplexObject.transformMatrixKey(); + } +}); + +const complexOld = benchmark(() => { + for (let i = 0; i < 1_000_000; i++) { + oldComplexObject.transformMatrixKey(); + } +}); + +console.log({ complexOld, complexNew }); + +const newSimpleObject = new FabricObject({ width: 100, height: 100 }); + +const oldSimpleObject = new OldObject({ width: 100, height: 100 }); + +const simpleNew = benchmark(() => { + for (let i = 0; i < 1_000_000; i++) { + newSimpleObject.transformMatrixKey(); + } +}); + +const simpleOld = benchmark(() => { + for (let i = 0; i < 1_000_000; i++) { + oldSimpleObject.transformMatrixKey(); + } +}); + +console.log({ simpleOld, simpleNew }); diff --git a/src/shapes/Object/ObjectGeometry.ts b/src/shapes/Object/ObjectGeometry.ts index fa7e6c0c320..05f2cbabeed 100644 --- a/src/shapes/Object/ObjectGeometry.ts +++ b/src/shapes/Object/ObjectGeometry.ts @@ -25,9 +25,10 @@ import type { StaticCanvas } from '../../canvas/StaticCanvas'; import { ObjectOrigin } from './ObjectOrigin'; import type { ObjectEvents } from '../../EventTypeDefs'; import type { ControlProps } from './types/ControlProps'; +import { resolveOrigin } from '../../util/misc/resolveOrigin'; type TMatrixCache = { - key: string; + key: number[]; value: TMat2D; }; @@ -437,40 +438,29 @@ export class ObjectGeometry this.aCoords = this.calcACoords(); } - transformMatrixKey(skipGroup = false): string { - const sep = '_'; - let prefix = ''; + transformMatrixKey(skipGroup = false): number[] { + let prefix: number[] = []; if (!skipGroup && this.group) { - prefix = this.group.transformMatrixKey(skipGroup) + sep; + prefix = this.group.transformMatrixKey(skipGroup); } - return ( - prefix + - this.top + - sep + - this.left + - sep + - this.scaleX + - sep + - this.scaleY + - sep + - this.skewX + - sep + - this.skewY + - sep + - this.angle + - sep + - this.originX + - sep + - this.originY + - sep + - this.width + - sep + - this.height + - sep + - this.strokeWidth + - this.flipX + - this.flipY + prefix.push( + this.top, + this.left, + this.width, + this.height, + this.scaleX, + this.scaleY, + this.angle, + this.strokeWidth, + this.skewX, + this.skewY, + +this.flipX, + +this.flipY, + resolveOrigin(this.originX), + resolveOrigin(this.originY) ); + + return prefix; } /** @@ -487,7 +477,7 @@ export class ObjectGeometry } const key = this.transformMatrixKey(skipGroup), cache = this.matrixCache; - if (cache && cache.key === key) { + if (cache && cache.key.every((x, i) => x === key[i])) { return cache.value; } if (this.group) { diff --git a/test/unit/object_geometry.js b/test/unit/object_geometry.js index 77b860e8cab..69ee6ab5dae 100644 --- a/test/unit/object_geometry.js +++ b/test/unit/object_geometry.js @@ -343,11 +343,11 @@ var key3 = cObj.transformMatrixKey(); cObj.width = 5; var key4 = cObj.transformMatrixKey(); - assert.notEqual(key1, key2, 'keys are different'); - assert.equal(key1, key3, 'keys are equal'); - assert.notEqual(key4, key2, 'keys are different'); - assert.notEqual(key4, key1, 'keys are different'); - assert.notEqual(key4, key3, 'keys are different'); + assert.notDeepEqual(key1, key2, 'keys are different'); + assert.deepEqual(key1, key3, 'keys are equal'); + assert.notDeepEqual(key4, key2, 'keys are different'); + assert.notDeepEqual(key4, key1, 'keys are different'); + assert.notDeepEqual(key4, key3, 'keys are different'); }); QUnit.test('transformMatrixKey depends from originX/originY', function(assert) { @@ -358,9 +358,9 @@ var key2 = cObj.transformMatrixKey(); cObj.originY = 'center'; var key3 = cObj.transformMatrixKey(); - assert.notEqual(key1, key2, 'keys are different origins 1'); - assert.notEqual(key1, key3, 'keys are different origins 2'); - assert.notEqual(key2, key3, 'keys are different origins 3'); + assert.notDeepEqual(key1, key2, 'keys are different origins 1'); + assert.notDeepEqual(key1, key3, 'keys are different origins 2'); + assert.notDeepEqual(key2, key3, 'keys are different origins 3'); }); QUnit.test('isOnScreen with object that include canvas', function(assert) {