diff --git a/packages/engine-formula/src/controller/set-feature-calculation.controller.ts b/packages/engine-formula/src/controller/set-feature-calculation.controller.ts index a28e9aa4afb..aa191fcbdcb 100644 --- a/packages/engine-formula/src/controller/set-feature-calculation.controller.ts +++ b/packages/engine-formula/src/controller/set-feature-calculation.controller.ts @@ -23,13 +23,15 @@ import { SetFeatureCalculationMutation, } from '../commands/mutations/set-feature-calculation.mutation'; import { IFeatureCalculationManagerService } from '../services/feature-calculation-manager.service'; +import { IDependencyManagerService } from '../services/dependency-manager.service'; @OnLifecycle(LifecycleStages.Ready, SetFeatureCalculationController) export class SetFeatureCalculationController extends Disposable { constructor( @ICommandService private readonly _commandService: ICommandService, @IFeatureCalculationManagerService - private readonly _featureCalculationManagerService: IFeatureCalculationManagerService + private readonly _featureCalculationManagerService: IFeatureCalculationManagerService, + @IDependencyManagerService private readonly _dependencyManagerService: IDependencyManagerService ) { super(); @@ -56,6 +58,7 @@ export class SetFeatureCalculationController extends Disposable { return; } const { featureId } = params; + this._dependencyManagerService.removeFeatureFormulaDependency(featureId); this._featureCalculationManagerService.remove(featureId); } }) diff --git a/packages/engine-formula/src/controller/set-other-formula.controller.ts b/packages/engine-formula/src/controller/set-other-formula.controller.ts index 8aec6d7b0b5..9b668ce5d25 100644 --- a/packages/engine-formula/src/controller/set-other-formula.controller.ts +++ b/packages/engine-formula/src/controller/set-other-formula.controller.ts @@ -23,12 +23,14 @@ import { type IOtherFormulaManagerSearchParam, IOtherFormulaManagerService, } from '../services/other-formula-manager.service'; +import { IDependencyManagerService } from '../services/dependency-manager.service'; @OnLifecycle(LifecycleStages.Ready, SetOtherFormulaController) export class SetOtherFormulaController extends Disposable { constructor( @ICommandService private readonly _commandService: ICommandService, - @IOtherFormulaManagerService private readonly _otherFormulaManagerService: IOtherFormulaManagerService + @IOtherFormulaManagerService private readonly _otherFormulaManagerService: IOtherFormulaManagerService, + @IDependencyManagerService private readonly _dependencyManagerService: IDependencyManagerService ) { super(); @@ -55,6 +57,7 @@ export class SetOtherFormulaController extends Disposable { return; } + this._dependencyManagerService.removeOtherFormulaDependency(params.unitId, params.subUnitId, params.formulaId); this._otherFormulaManagerService.remove(params); } }) diff --git a/packages/engine-formula/src/engine/dependency/dependency-tree.ts b/packages/engine-formula/src/engine/dependency/dependency-tree.ts index 2c45d7566fa..4099102c35c 100644 --- a/packages/engine-formula/src/engine/dependency/dependency-tree.ts +++ b/packages/engine-formula/src/engine/dependency/dependency-tree.ts @@ -77,6 +77,8 @@ export class FormulaDependencyTree extends Disposable { private _state = FDtreeStateType.DEFAULT; override dispose(): void { + super.dispose(); + this.children.forEach((tree) => { tree.dispose(); }); @@ -278,6 +280,10 @@ export class FormulaDependencyTreeCache extends Disposable { return this._cacheItems.size; } + get length() { + return this._cacheItems.size; + } + add(unitRangeWithToken: IUnitRangeWithToken, tree: FormulaDependencyTree) { const { token } = unitRangeWithToken; if (!this._cacheItems.has(token)) { diff --git a/packages/engine-formula/src/engine/dependency/formula-dependency.ts b/packages/engine-formula/src/engine/dependency/formula-dependency.ts index f5468431323..e9faa36f06f 100644 --- a/packages/engine-formula/src/engine/dependency/formula-dependency.ts +++ b/packages/engine-formula/src/engine/dependency/formula-dependency.ts @@ -38,6 +38,7 @@ import { Interpreter } from '../interpreter/interpreter'; import type { BaseReferenceObject } from '../reference-object/base-reference-object'; import type { PreCalculateNodeType } from '../utils/node-type'; import { serializeRangeToRefString } from '../utils/reference'; +import { IDependencyManagerService } from '../../services/dependency-manager.service'; import type { IUnitRangeWithToken } from './dependency-tree'; import { FormulaDependencyTree, FormulaDependencyTreeCache } from './dependency-tree'; @@ -59,7 +60,8 @@ export class FormulaDependencyGenerator extends Disposable { private readonly _featureCalculationManagerService: IFeatureCalculationManagerService, @Inject(Interpreter) private readonly _interpreter: Interpreter, @Inject(AstTreeBuilder) private readonly _astTreeBuilder: AstTreeBuilder, - @Inject(Lexer) private readonly _lexer: Lexer + @Inject(Lexer) private readonly _lexer: Lexer, + @IDependencyManagerService private readonly _dependencyManagerService: IDependencyManagerService ) { super(); } @@ -173,6 +175,11 @@ export class FormulaDependencyGenerator extends Disposable { return true; } const { f: formulaString, x, y } = formulaDataItem; + + if (this._dependencyManagerService.hasFormulaDependency(unitId, sheetId, row, column)) { + return true; + } + const node = this._generateAstNode(formulaString, x, y); const FDtree = new FormulaDependencyTree(); @@ -189,6 +196,8 @@ export class FormulaDependencyGenerator extends Disposable { FDtree.rowCount = sheetItem.rowCount; FDtree.columnCount = sheetItem.columnCount; + this._dependencyManagerService.addFormulaDependency(unitId, sheetId, row, column, FDtree); + treeList.push(FDtree); }); } @@ -216,6 +225,10 @@ export class FormulaDependencyGenerator extends Disposable { const subFormulaDataKeys = Object.keys(subFormulaData); for (const subFormulaDataId of subFormulaDataKeys) { + if (this._dependencyManagerService.hasOtherFormulaDependency(unitId, subUnitId, subFormulaDataId)) { + continue; + } + const formulaDataItem = subFormulaData[subFormulaDataId]; const { f: formulaString } = formulaDataItem; @@ -231,6 +244,8 @@ export class FormulaDependencyGenerator extends Disposable { FDtree.formulaId = subFormulaDataId; + this._dependencyManagerService.addOtherFormulaDependency(unitId, subUnitId, subFormulaDataId, FDtree); + treeList.push(FDtree); } } @@ -243,6 +258,11 @@ export class FormulaDependencyGenerator extends Disposable { */ this._featureCalculationManagerService.getReferenceExecutorMap().forEach((params, featureId) => { const { unitId, subUnitId, dependencyRanges, getDirtyData } = params; + + if (this._dependencyManagerService.hasFeatureFormulaDependency(unitId, subUnitId, featureId)) { + return true; + } + const FDtree = new FormulaDependencyTree(); FDtree.unitId = unitId; @@ -259,6 +279,8 @@ export class FormulaDependencyGenerator extends Disposable { }; }); + this._dependencyManagerService.addFeatureFormulaDependency(featureId, FDtree); + treeList.push(FDtree); }); @@ -485,29 +507,31 @@ export class FormulaDependencyGenerator extends Disposable { const existTree = new Set(); const forceCalculate = this._currentConfigService.isForceCalculate(); - let dependencyAlgorithm = true; + let allTree: FormulaDependencyTree[] = []; if (dependencyTreeCache.size() > treeList.length) { - dependencyAlgorithm = false; + allTree = this._dependencyManagerService.buildDependencyTree(treeList); + } else { + allTree = this._dependencyManagerService.buildDependencyTree(dependencyTreeCache); } - for (let i = 0, len = treeList.length; i < len; i++) { - const tree = treeList[i]; - - if (dependencyAlgorithm) { - dependencyTreeCache.dependency(tree); - } else { - for (let m = 0, mLen = treeList.length; m < mLen; m++) { - const treeMatch = treeList[m]; - if (tree === treeMatch) { - continue; - } - - if (tree.dependency(treeMatch)) { - tree.pushChildren(treeMatch); - } - } - } + for (let i = 0, len = allTree.length; i < len; i++) { + const tree = allTree[i]; + + // if (dependencyAlgorithm) { + // dependencyTreeCache.dependency(tree); + // } else { + // for (let m = 0, mLen = treeList.length; m < mLen; m++) { + // const treeMatch = treeList[m]; + // if (tree === treeMatch) { + // continue; + // } + + // if (tree.dependency(treeMatch)) { + // tree.pushChildren(treeMatch); + // } + // } + // } /** * forceCalculate: Mandatory calculation, adding all formulas to dependencies diff --git a/packages/engine-formula/src/plugin.ts b/packages/engine-formula/src/plugin.ts index e5009287c88..3ded69aac64 100644 --- a/packages/engine-formula/src/plugin.ts +++ b/packages/engine-formula/src/plugin.ts @@ -54,6 +54,7 @@ import { FunctionService, IFunctionService } from './services/function.service'; import { IOtherFormulaManagerService, OtherFormulaManagerService } from './services/other-formula-manager.service'; import { FormulaRuntimeService, IFormulaRuntimeService } from './services/runtime.service'; import { ISuperTableService, SuperTableService } from './services/super-table.service'; +import { DependencyManagerService, IDependencyManagerService } from './services/dependency-manager.service'; const PLUGIN_NAME = 'base-formula-engine'; @@ -107,6 +108,7 @@ export class UniverFormulaEnginePlugin extends Plugin { [ISuperTableService, { useClass: SuperTableService }], [IFormulaCurrentConfigService, { useClass: FormulaCurrentConfigService }], [IFormulaRuntimeService, { useClass: FormulaRuntimeService }], + [IDependencyManagerService, { useClass: DependencyManagerService }], //Controller [CalculateController], diff --git a/packages/engine-formula/src/services/dependency-manager.service.ts b/packages/engine-formula/src/services/dependency-manager.service.ts new file mode 100644 index 00000000000..de0d5086885 --- /dev/null +++ b/packages/engine-formula/src/services/dependency-manager.service.ts @@ -0,0 +1,242 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Nullable } from '@univerjs/core'; +import { Disposable, ObjectMatrix } from '@univerjs/core'; +import { createIdentifier } from '@wendellhu/redi'; +import { FormulaDependencyTreeCache } from '../engine/dependency/dependency-tree'; +import type { FormulaDependencyTree } from '../engine/dependency/dependency-tree'; + +export interface IOtherFormulaDependencyParam { + [unitId: string]: Nullable<{ [sheetId: string]: { [formulaId: string]: Nullable } }>; +} + +export interface IFormulaDependencyParam { + [unitId: string]: Nullable<{ [sheetId: string]: ObjectMatrix> }>; +} + +export interface IDependencyManagerService { + dispose(): void; + + getAllTree(): FormulaDependencyTree[]; + + buildDependencyTree(shouldBeBuildTrees: FormulaDependencyTree[] | FormulaDependencyTreeCache): FormulaDependencyTree[]; + + clearDependencyForTree(shouldBeClearTree: Nullable): void; + + reset(): void; + + addOtherFormulaDependency(unitId: string, sheetId: string, formulaId: string, dependencyTree: FormulaDependencyTree): void; + + removeOtherFormulaDependency(unitId: string, sheetId: string, formulaId: string): void; + + hasOtherFormulaDependency(unitId: string, sheetId: string, formulaId: string): boolean; + + addFeatureFormulaDependency(featureId: string, dependencyTree: FormulaDependencyTree): void; + + removeFeatureFormulaDependency(featureId: string): void; + + hasFeatureFormulaDependency(unitId: string, sheetId: string, featureId: string): boolean; + + addFormulaDependency(unitId: string, sheetId: string, row: number, column: number, dependencyTree: FormulaDependencyTree): void; + + removeFormulaDependency(unitId: string, sheetId: string, row: number, column: number): void; + + hasFormulaDependency(unitId: string, sheetId: string, row: number, column: number): boolean; +} + +/** + * Passively marked as dirty, register the reference and execution actions of the feature plugin. + * After execution, a dirty area and calculated data will be returned, + * causing the formula to be marked dirty again, + * thereby completing the calculation of the entire dependency tree. + */ +export class DependencyManagerService extends Disposable implements IDependencyManagerService { + private _otherFormulaData: IOtherFormulaDependencyParam = {}; + + private _featureFormulaData: Map = new Map(); + + private _formulaData: IFormulaDependencyParam = {}; + + override dispose(): void { + this._otherFormulaData = {}; + this._featureFormulaData.clear(); + this._formulaData = {}; + } + + /** + * Get all FormulaDependencyTree from _otherFormulaData, _featureFormulaData, _formulaData + * return FormulaDependencyTree[] + */ + getAllTree() { + const trees: FormulaDependencyTree[] = []; + + Object.values(this._otherFormulaData).forEach((unit) => { + if (unit == null) { + return true; + } + Object.values(unit).forEach((sheet) => { + Object.values(sheet).forEach((formula) => { + formula && trees.push(formula); + }); + }); + }); + + this._featureFormulaData.forEach((feature) => { + feature && trees.push(feature); + }); + + Object.values(this._formulaData).map((unit) => { + if (unit == null) { + return []; + } + return Object.values(unit).forEach((sheet) => { + return sheet.forValue((row, col, item) => { + item && trees.push(item); + }); + }); + }); + + return trees; + } + + buildDependencyTree(shouldBeBuildTrees: FormulaDependencyTree[] | FormulaDependencyTreeCache): FormulaDependencyTree[] { + const allTrees = this.getAllTree(); + if (shouldBeBuildTrees.length === 0) { + return allTrees; + } + this._buildDependencyTree(allTrees, shouldBeBuildTrees); + return allTrees; + } + + /** + * Build the dependency relationship between the trees. + * @param allTrees all FormulaDependencyTree + * @param shouldBeBuildTrees FormulaDependencyTree[] | FormulaDependencyTreeCache + */ + private _buildDependencyTree(allTrees: FormulaDependencyTree[], shouldBeBuildTrees: FormulaDependencyTree[] | FormulaDependencyTreeCache) { + allTrees.forEach((tree) => { + if (shouldBeBuildTrees instanceof FormulaDependencyTreeCache) { + shouldBeBuildTrees.dependency(tree); + } else { + shouldBeBuildTrees.forEach((shouldBeBuildTree) => { + if (tree === shouldBeBuildTree) { + return true; + } + + if (shouldBeBuildTree.dependency(tree)) { + shouldBeBuildTree.pushChildren(tree); + } + }); + } + }); + } + + /** + * Clear the dependency relationship of the tree. + * establish the relationship between the parent and the child. + * @param shouldBeClearTree + */ + clearDependencyForTree(shouldBeClearTree: Nullable) { + if (shouldBeClearTree == null) { + return; + } + + const parents = shouldBeClearTree.parents; + + const children = shouldBeClearTree.children; + + parents.forEach((parent) => { + parent.children = parent.children.filter((child) => child !== shouldBeClearTree); + }); + + children.forEach((child) => { + child.parents = child.parents.filter((parent) => parent !== shouldBeClearTree); + }); + + this._buildDependencyTree(parents, children); + + shouldBeClearTree.dispose(); + } + + reset() { + this._otherFormulaData = {}; + this._featureFormulaData.clear(); + this._formulaData = {}; + } + + addOtherFormulaDependency(unitId: string, sheetId: string, formulaId: string, dependencyTree: FormulaDependencyTree) { + if (!this._otherFormulaData[unitId]) { + this._otherFormulaData[unitId] = {}; + } + if (!this._otherFormulaData[unitId]![sheetId]) { + this._otherFormulaData[unitId]![sheetId] = {}; + } + this._otherFormulaData[unitId]![sheetId][formulaId] = dependencyTree; + } + + removeOtherFormulaDependency(unitId: string, sheetId: string, formulaId: string) { + if (this._otherFormulaData[unitId] && this._otherFormulaData[unitId]![sheetId]) { + const deleteTree = this._otherFormulaData[unitId]![sheetId][formulaId]; + this.clearDependencyForTree(deleteTree); + delete this._otherFormulaData[unitId]![sheetId][formulaId]; + } + } + + hasOtherFormulaDependency(unitId: string, sheetId: string, formulaId: string) { + return this._otherFormulaData[unitId]?.[sheetId]?.[formulaId] != null; + } + + addFeatureFormulaDependency(featureId: string, dependencyTree: FormulaDependencyTree) { + this._featureFormulaData.set(featureId, dependencyTree); + } + + removeFeatureFormulaDependency(featureId: string) { + const deleteTree = this._featureFormulaData.get(featureId); + this.clearDependencyForTree(deleteTree); + this._featureFormulaData.delete(featureId); + } + + hasFeatureFormulaDependency(featureId: string) { + return this._featureFormulaData.has(featureId); + } + + addFormulaDependency(unitId: string, sheetId: string, row: number, column: number, dependencyTree: FormulaDependencyTree) { + if (!this._formulaData[unitId]) { + this._formulaData[unitId] = {}; + } + if (!this._formulaData[unitId]![sheetId]) { + this._formulaData[unitId]![sheetId] = new ObjectMatrix>(); + } + this._formulaData[unitId]![sheetId].setValue(row, column, dependencyTree); + } + + removeFormulaDependency(unitId: string, sheetId: string, row: number, column: number) { + if (this._formulaData[unitId] && this._formulaData[unitId]![sheetId]) { + const deleteTree = this._formulaData[unitId]![sheetId].getValue(row, column); + this.clearDependencyForTree(deleteTree); + this._formulaData[unitId]![sheetId].realDeleteValue(row, column); + } + } + + hasFormulaDependency(unitId: string, sheetId: string, row: number, column: number) { + return this._formulaData[unitId]?.[sheetId]?.getValue(row, column) != null; + } +} + +export const IDependencyManagerService = createIdentifier( + 'univer.formula.dependency-manager.service' +);