Skip to content

Commit

Permalink
feat(engine-formula): add global computing status service (#4602)
Browse files Browse the repository at this point in the history
  • Loading branch information
wzhudev authored Feb 10, 2025
1 parent bb81076 commit 15a943c
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const SetFormulaCalculationStopMutation: IMutation<ISetFormulaCalculation
handler: () => true,
};

// TODO: this name lacks Params
export interface ISetFormulaCalculationNotificationMutation {
functionsExecutedState?: FormulaExecutedStateType;
stageInfo?: IExecutionInProgressParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<boolean>((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);
}
}
1 change: 1 addition & 0 deletions packages/engine-formula/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
16 changes: 6 additions & 10 deletions packages/engine-formula/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -106,44 +108,38 @@ export class UniverFormulaEnginePlugin extends Plugin {
}

private _initialize() {
const shouldPerformComputing = !this._config.notExecuteFormula;
// worker and main thread
const dependencies: Dependency[] = [
// Services
[IFunctionService, { useClass: FunctionService }],
[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],
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ComputingStatus>[] = [];

private readonly _computingStatus$ = new BehaviorSubject<ComputingStatus>(true);
readonly computingStatus$ = this._computingStatus$.pipe(distinctUntilChanged());

private _computingSubscription: Nullable<Subscription>;

override dispose(): void {
super.dispose();

this._computingSubscription?.unsubscribe();

this._computingStatus$.next(true);
this._computingStatus$.complete();
}

pushComputingStatusSubject(subject: BehaviorSubject<ComputingStatus>): 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));
}
}
18 changes: 9 additions & 9 deletions packages/engine-formula/src/services/runtime.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -254,6 +252,8 @@ export class FormulaRuntimeService extends Disposable implements IFormulaRuntime
}

override dispose(): void {
super.dispose();

this.reset();
this._runtimeFeatureCellData = {};
this._runtimeFeatureRange = {};
Expand Down

0 comments on commit 15a943c

Please sign in to comment.