From 15a943cac740d653711912795f079f1491d9a881 Mon Sep 17 00:00:00 2001 From: Wenzhao Hu Date: Mon, 10 Feb 2025 13:49:12 +0800 Subject: [PATCH] feat(engine-formula): add global computing status service (#4602) --- .../set-formula-calculation.mutation.ts | 1 + .../src/controller/calculate.controller.ts | 3 +- .../controller/computing-status.controller.ts | 64 +++++++++++++++++ packages/engine-formula/src/index.ts | 1 + packages/engine-formula/src/plugin.ts | 16 ++--- .../global-computing-status.service.ts | 69 +++++++++++++++++++ .../src/services/runtime.service.ts | 18 ++--- 7 files changed, 152 insertions(+), 20 deletions(-) create mode 100644 packages/engine-formula/src/controller/computing-status.controller.ts create mode 100644 packages/engine-formula/src/services/global-computing-status.service.ts diff --git a/packages/engine-formula/src/commands/mutations/set-formula-calculation.mutation.ts b/packages/engine-formula/src/commands/mutations/set-formula-calculation.mutation.ts index a5bc0260f4c6..cab94511e10e 100644 --- a/packages/engine-formula/src/commands/mutations/set-formula-calculation.mutation.ts +++ b/packages/engine-formula/src/commands/mutations/set-formula-calculation.mutation.ts @@ -45,6 +45,7 @@ export const SetFormulaCalculationStopMutation: IMutation true, }; +// TODO: this name lacks Params export interface ISetFormulaCalculationNotificationMutation { functionsExecutedState?: FormulaExecutedStateType; stageInfo?: IExecutionInProgressParams; diff --git a/packages/engine-formula/src/controller/calculate.controller.ts b/packages/engine-formula/src/controller/calculate.controller.ts index b945fcdfea73..bf0e076c0db2 100644 --- a/packages/engine-formula/src/controller/calculate.controller.ts +++ b/packages/engine-formula/src/controller/calculate.controller.ts @@ -19,6 +19,7 @@ import type { ICommandInfo } from '@univerjs/core'; import type { ISetArrayFormulaDataMutationParams } from '../commands/mutations/set-array-formula-data.mutation'; import type { ISetFormulaCalculationStartMutation } from '../commands/mutations/set-formula-calculation.mutation'; import type { IFormulaDirtyData } from '../services/current-data.service'; +import type { IAllRuntimeData } from '../services/runtime.service'; import { Disposable, ICommandService, Inject } from '@univerjs/core'; import { convertRuntimeToUnitData } from '../basics/runtime'; import { SetArrayFormulaDataMutation } from '../commands/mutations/set-array-formula-data.mutation'; @@ -30,7 +31,7 @@ import { } from '../commands/mutations/set-formula-calculation.mutation'; import { FormulaDataModel } from '../models/formula-data.model'; import { ICalculateFormulaService } from '../services/calculate-formula.service'; -import { FormulaExecutedStateType, type IAllRuntimeData } from '../services/runtime.service'; +import { FormulaExecutedStateType } from '../services/runtime.service'; import { DEFAULT_CYCLE_REFERENCE_COUNT } from './config.schema'; export class CalculateController extends Disposable { diff --git a/packages/engine-formula/src/controller/computing-status.controller.ts b/packages/engine-formula/src/controller/computing-status.controller.ts new file mode 100644 index 000000000000..e1588fb0a338 --- /dev/null +++ b/packages/engine-formula/src/controller/computing-status.controller.ts @@ -0,0 +1,64 @@ +/** + * 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 { ICommandInfo } from '@univerjs/core'; +import type { ISetFormulaCalculationNotificationMutation } from '../commands/mutations/set-formula-calculation.mutation'; +import { Disposable, DisposableCollection, ICommandService, Inject } from '@univerjs/core'; +import { BehaviorSubject, distinctUntilChanged, Observable, shareReplay } from 'rxjs'; +import { SetFormulaCalculationNotificationMutation } from '../commands/mutations/set-formula-calculation.mutation'; +import { GlobalComputingStatusService } from '../services/global-computing-status.service'; +import { FormulaExecuteStageType } from '../services/runtime.service'; + +// TODO@wzhudev: move logics in Facade to this place. + +/** + * This controller monitors the calculating status of the formula engine, + * and expose some internal status to the outside. + * + * @ignore + */ +export class ComputingStatusReporterController extends Disposable { + private _computingCompleted$ = new Observable((observe) => { + this._commandService.onCommandExecuted((command: ICommandInfo) => { + if (command.id !== SetFormulaCalculationNotificationMutation.id) return; + + const params = command.params as ISetFormulaCalculationNotificationMutation; + if (params.stageInfo) { + return observe.next( + params.stageInfo.stage === FormulaExecuteStageType.IDLE + || params.stageInfo.stage === FormulaExecuteStageType.CALCULATION_COMPLETED + ); + } + }); + }).pipe( + distinctUntilChanged(), + shareReplay() + ); + + constructor( + @ICommandService private readonly _commandService: ICommandService, + @Inject(GlobalComputingStatusService) private readonly _globalComputingSrv: GlobalComputingStatusService + ) { + super(); + + const disposables = new DisposableCollection(); + const subject = new BehaviorSubject(true); + disposables.add(this._globalComputingSrv.pushComputingStatusSubject(subject)); + disposables.add(this._computingCompleted$.subscribe((completed) => subject.next(completed))); + disposables.add(() => subject.complete()); + this.disposeWithMe(disposables); + } +} diff --git a/packages/engine-formula/src/index.ts b/packages/engine-formula/src/index.ts index 6b27e7bca200..66c24913d5d3 100644 --- a/packages/engine-formula/src/index.ts +++ b/packages/engine-formula/src/index.ts @@ -140,6 +140,7 @@ export { FormulaDataModel } from './models/formula-data.model'; export { initSheetFormulaData } from './models/formula-data.model'; export type { IRangeChange } from './models/formula-data.model'; export { UniverFormulaEnginePlugin } from './plugin'; +export { GlobalComputingStatusService } from './services/global-computing-status.service'; export { IActiveDirtyManagerService } from './services/active-dirty-manager.service'; export { ActiveDirtyManagerService } from './services/active-dirty-manager.service'; export { CalculateFormulaService, ICalculateFormulaService } from './services/calculate-formula.service'; diff --git a/packages/engine-formula/src/plugin.ts b/packages/engine-formula/src/plugin.ts index 75444ef34a15..acfbca7eb6cd 100644 --- a/packages/engine-formula/src/plugin.ts +++ b/packages/engine-formula/src/plugin.ts @@ -18,6 +18,7 @@ import type { Dependency } from '@univerjs/core'; import type { IUniverEngineFormulaConfig } from './controller/config.schema'; import { IConfigService, Inject, Injector, merge, Plugin, touchDependencies } from '@univerjs/core'; import { CalculateController } from './controller/calculate.controller'; +import { ComputingStatusReporterController } from './controller/computing-status.controller'; import { defaultPluginConfig, ENGINE_FORMULA_PLUGIN_CONFIG_KEY } from './controller/config.schema'; import { FormulaController } from './controller/formula.controller'; import { SetDependencyController } from './controller/set-dependency.controller'; @@ -50,6 +51,7 @@ import { IFeatureCalculationManagerService, } from './services/feature-calculation-manager.service'; import { FunctionService, IFunctionService } from './services/function.service'; +import { GlobalComputingStatusService } from './services/global-computing-status.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'; @@ -106,6 +108,7 @@ export class UniverFormulaEnginePlugin extends Plugin { } private _initialize() { + const shouldPerformComputing = !this._config.notExecuteFormula; // worker and main thread const dependencies: Dependency[] = [ // Services @@ -113,37 +116,30 @@ export class UniverFormulaEnginePlugin extends Plugin { [IDefinedNamesService, { useClass: DefinedNamesService }], [IActiveDirtyManagerService, { useClass: ActiveDirtyManagerService }], [ISuperTableService, { useClass: SuperTableService }], - + [GlobalComputingStatusService], // Models [FormulaDataModel], - // Engine [LexerTreeBuilder], - //Controllers [FormulaController], [SetSuperTableController], + [ComputingStatusReporterController], ]; - if (!this._config?.notExecuteFormula) { - // only worker + if (shouldPerformComputing) { dependencies.push( // Services - [IOtherFormulaManagerService, { useClass: OtherFormulaManagerService }], [IFormulaRuntimeService, { useClass: FormulaRuntimeService }], [IFormulaCurrentConfigService, { useClass: FormulaCurrentConfigService }], - [IFeatureCalculationManagerService, { useClass: FeatureCalculationManagerService }], - //Controller [CalculateController], [SetOtherFormulaController], [SetDependencyController], [SetFeatureCalculationController], - // Calculation engine - [Interpreter], [AstTreeBuilder], [Lexer], diff --git a/packages/engine-formula/src/services/global-computing-status.service.ts b/packages/engine-formula/src/services/global-computing-status.service.ts new file mode 100644 index 000000000000..224ae299ef35 --- /dev/null +++ b/packages/engine-formula/src/services/global-computing-status.service.ts @@ -0,0 +1,69 @@ +/** + * 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 { IDisposable, Nullable } from '@univerjs/core'; +import type { Subscription } from 'rxjs'; +import { Disposable } from '@univerjs/core'; +import { BehaviorSubject, combineLatest, distinctUntilChanged, map } from 'rxjs'; + +export type ComputingStatus = boolean; + +export class GlobalComputingStatusService extends Disposable { + private _allSubjects: BehaviorSubject[] = []; + + private readonly _computingStatus$ = new BehaviorSubject(true); + readonly computingStatus$ = this._computingStatus$.pipe(distinctUntilChanged()); + + private _computingSubscription: Nullable; + + override dispose(): void { + super.dispose(); + + this._computingSubscription?.unsubscribe(); + + this._computingStatus$.next(true); + this._computingStatus$.complete(); + } + + pushComputingStatusSubject(subject: BehaviorSubject): IDisposable { + this._allSubjects.push(subject); + this._updateComputingObservable(); + + return { + dispose: () => { + const index = this._allSubjects.indexOf(subject); + if (index !== -1) { + this._allSubjects.splice(index, 1); + } + + this._updateComputingObservable(); + }, + }; + } + + private _updateComputingObservable(): void { + this._computingSubscription?.unsubscribe(); + + if (this._allSubjects.length === 0) { + this._computingStatus$.next(true); + return; + } + + this._computingSubscription = combineLatest(this._allSubjects) + .pipe(map((values) => values.every((v) => v))) + .subscribe((computing) => this._computingStatus$.next(computing)); + } +} diff --git a/packages/engine-formula/src/services/runtime.service.ts b/packages/engine-formula/src/services/runtime.service.ts index 75314dceeba2..6d2dc0ef5e5c 100644 --- a/packages/engine-formula/src/services/runtime.service.ts +++ b/packages/engine-formula/src/services/runtime.service.ts @@ -24,6 +24,7 @@ import type { import type { BaseAstNode } from '../engine/ast-node/base-ast-node'; import type { BaseReferenceObject, FunctionVariantType } from '../engine/reference-object/base-reference-object'; import type { ArrayValueObject } from '../engine/value-object/array-value-object'; +import type { BaseValueObject } from '../engine/value-object/base-value-object'; import { createIdentifier, Disposable, ObjectMatrix } from '@univerjs/core'; import { isInDirtyRange } from '../basics/dirty'; import { ErrorType } from '../basics/error-type'; @@ -32,17 +33,12 @@ import { getRuntimeFeatureCell } from '../engine/utils/get-runtime-feature-cell' import { clearNumberFormatTypeCache, clearStringToNumberPatternCache } from '../engine/utils/numfmt-kit'; import { clearReferenceToRangeCache } from '../engine/utils/reference-cache'; import { objectValueToCellValue } from '../engine/utils/value-object'; -import { type BaseValueObject, ErrorValueObject } from '../engine/value-object/base-value-object'; +import { ErrorValueObject } from '../engine/value-object/base-value-object'; import { IFormulaCurrentConfigService } from './current-data.service'; /** - * IDLE: Idle phase of the formula engine. - * - * DEPENDENCY: Dependency calculation phase, where the formulas that need to be calculated are determined by the modified area, - * as well as their dependencies. This outputs an array of formulas to execute. - * - * INTERPRETER:Formula execution phase, where the calculation of formulas begins. - * + * The formula engine has a lot of stages. IDLE and CALCULATION_COMPLETED can be considered as + * the computing has completed. */ export enum FormulaExecuteStageType { IDLE, @@ -225,7 +221,9 @@ export class FormulaRuntimeService extends Disposable implements IFormulaRuntime private _isCycleDependency: boolean = false; - constructor(@IFormulaCurrentConfigService private readonly _currentConfigService: IFormulaCurrentConfigService) { + constructor( + @IFormulaCurrentConfigService private readonly _currentConfigService: IFormulaCurrentConfigService + ) { super(); } @@ -254,6 +252,8 @@ export class FormulaRuntimeService extends Disposable implements IFormulaRuntime } override dispose(): void { + super.dispose(); + this.reset(); this._runtimeFeatureCellData = {}; this._runtimeFeatureRange = {};