Skip to content

Commit

Permalink
fix(formula): set notification for formula calculate
Browse files Browse the repository at this point in the history
  • Loading branch information
Dushusir committed Oct 21, 2024
1 parent 50dc1d2 commit a9d5579
Show file tree
Hide file tree
Showing 14 changed files with 456 additions and 135 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
} from '../../basics/common';

import type { IFormulaDirtyData } from '../../services/current-data.service';
import type { FormulaExecutedStateType, IExecutionInProgressParams } from '../../services/runtime.service';
import { CommandType } from '@univerjs/core';

export interface ISetFormulaCalculationStartMutation extends IFormulaDirtyData {
Expand All @@ -37,9 +38,17 @@ export const SetFormulaCalculationStartMutation: IMutation<ISetFormulaCalculatio
handler: () => true,
};

export interface ISetFormulaCalculationStopMutation {}

export const SetFormulaCalculationStopMutation: IMutation<ISetFormulaCalculationStopMutation> = {
id: 'formula.mutation.set-formula-calculation-stop',
type: CommandType.MUTATION,
handler: () => true,
};

export interface ISetFormulaCalculationNotificationMutation {
startCalculate: boolean;
formulaCount: number;
functionsExecutedState?: FormulaExecutedStateType;
stageInfo?: IExecutionInProgressParams;
}

export const SetFormulaCalculationNotificationMutation: IMutation<ISetFormulaCalculationNotificationMutation> = {
Expand Down
55 changes: 32 additions & 23 deletions packages/engine-formula/src/controller/calculate.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,21 @@ import type { ICommandInfo, IUnitRange } from '@univerjs/core';
import type { IDirtyUnitFeatureMap, IDirtyUnitOtherFormulaMap, IDirtyUnitSheetDefinedNameMap, IDirtyUnitSheetNameMap, IFormulaData } from '../basics/common';

import type { ISetArrayFormulaDataMutationParams } from '../commands/mutations/set-array-formula-data.mutation';
import type { ISetFormulaCalculationNotificationMutation, ISetFormulaCalculationStartMutation } from '../commands/mutations/set-formula-calculation.mutation';
import type { ISetFormulaCalculationStartMutation } from '../commands/mutations/set-formula-calculation.mutation';
import type { ISetFormulaDataMutationParams } from '../commands/mutations/set-formula-data.mutation';
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';
import {
SetFormulaCalculationNotificationMutation,
SetFormulaCalculationResultMutation,
SetFormulaCalculationStartMutation,
SetFormulaCalculationStopMutation,
} from '../commands/mutations/set-formula-calculation.mutation';
import { SetFormulaDataMutation } from '../commands/mutations/set-formula-data.mutation';
import { FormulaDataModel } from '../models/formula-data.model';
import { CalculateFormulaService } from '../services/calculate-formula.service';
import { FormulaExecutedStateType, type IAllRuntimeData } from '../services/runtime.service';

export class CalculateController extends Disposable {
constructor(
Expand All @@ -52,7 +53,9 @@ export class CalculateController extends Disposable {
private _commandExecutedListener() {
this.disposeWithMe(
this._commandService.onCommandExecuted((command: ICommandInfo) => {
if (command.id === SetFormulaDataMutation.id) {
if (command.id === SetFormulaCalculationStopMutation.id) {
this._calculateFormulaService.stopFormulaExecution();
} else if (command.id === SetFormulaDataMutation.id) {
const formulaData = (command.params as ISetFormulaDataMutationParams).formulaData as IFormulaData;

// formulaData is the incremental data sent from the main thread and needs to be merged into formulaDataModel
Expand Down Expand Up @@ -126,42 +129,48 @@ export class CalculateController extends Disposable {

// Notification
private _initialExecuteFormulaListener() {
this._calculateFormulaService.executionStartListener$.subscribe(() => {
const params: ISetFormulaCalculationNotificationMutation = {
startCalculate: true,
formulaCount: 0,
};
/**
* Assignment operation after formula calculation.
*/
this._calculateFormulaService.executionCompleteListener$.subscribe((data) => {
const functionsExecutedState = data.functionsExecutedState;
switch (functionsExecutedState) {
case FormulaExecutedStateType.NOT_EXECUTED:
break;
case FormulaExecutedStateType.STOP_EXECUTION:
break;
case FormulaExecutedStateType.SUCCESS:
this._applyResult(data);
break;
case FormulaExecutedStateType.INITIAL:
break;
}

this._commandService.executeCommand(
SetFormulaCalculationNotificationMutation.id,
params,
{
functionsExecutedState,
},
{
onlyLocal: true,
}
);
});

this._calculateFormulaService.executionInProgressListener$.subscribe((formulaCount) => {
const params: ISetFormulaCalculationNotificationMutation = {
startCalculate: false,
formulaCount,
};

/**
* Assignment operation after formula calculation.
*/
this._calculateFormulaService.executionInProgressListener$.subscribe((data) => {
this._commandService.executeCommand(
SetFormulaCalculationNotificationMutation.id,
params,
{
stageInfo: data,
},
{
onlyLocal: true,
}
);
});

/**
* Assignment operation after formula calculation.
*/
this._calculateFormulaService.executionCompleteListener$.subscribe((data) => {
this._applyResult(data);
});
}

private async _applyResult(data: IAllRuntimeData) {
Expand Down
8 changes: 7 additions & 1 deletion packages/engine-formula/src/controller/config.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/

import type { Ctor } from '@univerjs/core';
import type { BaseFunction } from '../functions/base-function';
import type { IFunctionNames } from '../basics/function';
import type { BaseFunction } from '../functions/base-function';

export const PLUGIN_CONFIG_KEY = 'engine-formula.config';

Expand All @@ -25,6 +25,12 @@ export const configSymbol = Symbol(PLUGIN_CONFIG_KEY);
export interface IUniverEngineFormulaConfig {
notExecuteFormula?: boolean;
function?: Array<[Ctor<BaseFunction>, IFunctionNames]>;

/**
* The formula calculation quantity interval for waiting for the main thread message in the worker. Each time the formula calculates the `intervalCount` quantity, it will receive a main thread message to support stopping the calculation. Default is 500
*/
intervalCount?: number;

}

export const defaultPluginConfig: IUniverEngineFormulaConfig = {};
2 changes: 2 additions & 0 deletions packages/engine-formula/src/controller/formula.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
SetFormulaCalculationNotificationMutation,
SetFormulaCalculationResultMutation,
SetFormulaCalculationStartMutation,
SetFormulaCalculationStopMutation,
} from '../commands/mutations/set-formula-calculation.mutation';
import { SetFormulaDataMutation } from '../commands/mutations/set-formula-data.mutation';
import { RemoveOtherFormulaMutation, SetOtherFormulaMutation } from '../commands/mutations/set-other-formula.mutation';
Expand Down Expand Up @@ -81,6 +82,7 @@ export class FormulaController extends Disposable {
SetFormulaDataMutation,
SetArrayFormulaDataMutation,
SetFormulaCalculationStartMutation,
SetFormulaCalculationStopMutation,
SetFormulaCalculationNotificationMutation,
SetFormulaCalculationResultMutation,

Expand Down
6 changes: 6 additions & 0 deletions packages/engine-formula/src/engine/interpreter/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ export class Interpreter extends Disposable {
}

private async _executeAsync(node: BaseAstNode, refOffsetX = 0, refOffsetY = 0): Promise<AstNodePromiseType> {
if (this._runtimeService.isStopExecution()) {
return Promise.resolve(AstNodePromiseType.ERROR);
}
const children = node.getChildren();
const childrenCount = children.length;
for (let i = 0; i < childrenCount; i++) {
Expand Down Expand Up @@ -144,6 +147,9 @@ export class Interpreter extends Disposable {
}

private _execute(node: BaseAstNode, refOffsetX = 0, refOffsetY = 0): AstNodePromiseType {
if (this._runtimeService.isStopExecution()) {
return AstNodePromiseType.ERROR;
}
const children = node.getChildren();
const childrenCount = children.length;
for (let i = 0; i < childrenCount; i++) {
Expand Down
4 changes: 2 additions & 2 deletions packages/engine-formula/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export {
SetFormulaCalculationNotificationMutation,
SetFormulaCalculationResultMutation,
SetFormulaCalculationStartMutation,
SetFormulaCalculationStopMutation,
} from './commands/mutations/set-formula-calculation.mutation';
export { type ISetFormulaDataMutationParams, SetFormulaDataMutation } from './commands/mutations/set-formula-data.mutation';
export { type IRemoveOtherFormulaMutationParams, type ISetOtherFormulaMutationParams, RemoveOtherFormulaMutation, SetOtherFormulaMutation } from './commands/mutations/set-other-formula.mutation';
Expand Down Expand Up @@ -151,8 +152,7 @@ export { FeatureCalculationManagerService, IFeatureCalculationManagerService } f
export { FunctionService } from './services/function.service';
export { IFunctionService } from './services/function.service';
export { IOtherFormulaManagerService, OtherFormulaManagerService } from './services/other-formula-manager.service';
export { type IAllRuntimeData } from './services/runtime.service';
export { FormulaRuntimeService, IFormulaRuntimeService } from './services/runtime.service';
export { FormulaExecutedStateType, FormulaExecuteStageType, FormulaRuntimeService, type IAllRuntimeData, type IExecutionInProgressParams, IFormulaRuntimeService } from './services/runtime.service';
export { ISuperTableService } from './services/super-table.service';
export { SuperTableService } from './services/super-table.service';
export { deserializeRangeWithSheetWithCache } from './engine/utils/reference-cache';
105 changes: 86 additions & 19 deletions packages/engine-formula/src/services/calculate-formula.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import type {
IRuntimeUnitDataType,
IUnitExcludedCell,
} from '../basics/common';
import type { LexerNode } from '../engine/analysis/lexer-node';
import type { IUniverEngineFormulaConfig } from '../controller/config.schema';

import type { IAllRuntimeData } from './runtime.service';
import type { LexerNode } from '../engine/analysis/lexer-node';
import type { IAllRuntimeData, IExecutionInProgressParams } from './runtime.service';
import {
Disposable,
IConfigService,
Expand All @@ -35,27 +36,26 @@ import {
import { Subject } from 'rxjs';
import { ErrorType } from '../basics/error-type';
import { CELL_INVERTED_INDEX_CACHE } from '../basics/inverted-index-cache';
import { PLUGIN_CONFIG_KEY } from '../controller/config.schema';
import { Lexer } from '../engine/analysis/lexer';
import { AstTreeBuilder } from '../engine/analysis/parser';
import { ErrorNode } from '../engine/ast-node/base-ast-node';
import { FormulaDependencyGenerator } from '../engine/dependency/formula-dependency';
import { Interpreter } from '../engine/interpreter/interpreter';
import { FORMULA_REF_TO_ARRAY_CACHE, type FunctionVariantType } from '../engine/reference-object/base-reference-object';
import { IFormulaCurrentConfigService } from './current-data.service';
import { IFormulaRuntimeService } from './runtime.service';
import { FormulaExecuteStageType, IFormulaRuntimeService } from './runtime.service';

export const DEFAULT_CYCLE_REFERENCE_COUNT = 1;

export const DEFAULT_INTERVAL_COUNT = 500;

export const CYCLE_REFERENCE_COUNT = 'cycleReferenceCount';

export const EVERY_N_FUNCTION_EXECUTION_PAUSE = 100;

export class CalculateFormulaService extends Disposable {
private readonly _executionStartListener$ = new Subject<boolean>();

readonly executionStartListener$ = this._executionStartListener$.asObservable();

private readonly _executionInProgressListener$ = new Subject<number>();
private readonly _executionInProgressListener$ = new Subject<IExecutionInProgressParams>();

readonly executionInProgressListener$ = this._executionInProgressListener$.asObservable();

Expand All @@ -75,6 +75,13 @@ export class CalculateFormulaService extends Disposable {
super();
}

/**
* Stop the execution of the formula.
*/
stopFormulaExecution() {
this._runtimeService.stopExecution();
}

/**
* When the feature is loading,
* the pre-calculated content needs to be input to the formula engine in advance,
Expand All @@ -91,7 +98,8 @@ export class CalculateFormulaService extends Disposable {
}

async execute(formulaDatasetConfig: IFormulaDatasetConfig) {
this._executionStartListener$.next(true);
this._runtimeService.setFormulaExecuteStage(FormulaExecuteStageType.START);
this._executionInProgressListener$.next(this._runtimeService.getRuntimeState());

this._currentConfigService.load(formulaDatasetConfig);

Expand All @@ -112,6 +120,10 @@ export class CalculateFormulaService extends Disposable {
}
}

this._runtimeService.setFormulaExecuteStage(FormulaExecuteStageType.CALCULATION_COMPLETED);

this._executionInProgressListener$.next(this._runtimeService.getRuntimeState());

this._executionCompleteListener$.next(this._runtimeService.getAllRuntimeData());

CELL_INVERTED_INDEX_CACHE.clear();
Expand Down Expand Up @@ -204,25 +216,70 @@ export class CalculateFormulaService extends Disposable {
return { dirtyRanges, excludedCell };
}

// eslint-disable-next-line max-lines-per-function
private async _apply(isArrayFormulaState = false) {
if (isArrayFormulaState) {
this._runtimeService.setFormulaExecuteStage(FormulaExecuteStageType.START_DEPENDENCY_ARRAY_FORMULA);
} else {
this._runtimeService.setFormulaExecuteStage(FormulaExecuteStageType.START_DEPENDENCY);
}

this._executionInProgressListener$.next(this._runtimeService.getRuntimeState());

const treeList = await this._formulaDependencyGenerator.generate();

const interpreter = this._interpreter;

if (!isArrayFormulaState) {
/**
* Prevent messages sent to the main thread from getting stuck
*/
let calCancelTask = () => {};
await new Promise((resolve) => {
this._executionInProgressListener$.next(treeList.length);
calCancelTask = requestImmediateMacroTask(resolve);
});
if (isArrayFormulaState) {
this._runtimeService.setFormulaExecuteStage(FormulaExecuteStageType.START_CALCULATION_ARRAY_FORMULA);

calCancelTask();
this._runtimeService.setTotalArrayFormulasToCalculate(treeList.length);
} else {
this._runtimeService.setFormulaExecuteStage(FormulaExecuteStageType.START_CALCULATION);

this._runtimeService.setTotalFormulasToCalculate(treeList.length);
}

this._executionInProgressListener$.next(this._runtimeService.getRuntimeState());

let pendingTasks: (() => void)[] = [];

const config = this._configService.getConfig(PLUGIN_CONFIG_KEY) as IUniverEngineFormulaConfig;
const intervalCount = config?.intervalCount || DEFAULT_INTERVAL_COUNT;

for (let i = 0, len = treeList.length; i < len; i++) {
// Execute the await every 100 iterations
if (i % intervalCount === 0) {
/**
* For every functions, execute a setTimeout to wait for external command input.
*/
await new Promise((resolve) => {
const calCancelTask = requestImmediateMacroTask(resolve);
pendingTasks.push(calCancelTask);
});

if (this._runtimeService.isStopExecution()) {
this._runtimeService.setFormulaExecuteStage(FormulaExecuteStageType.IDLE);
this._runtimeService.markedAsStopFunctionsExecuted();
this._executionCompleteListener$.next(this._runtimeService.getAllRuntimeData());
return;
}

if (isArrayFormulaState) {
this._runtimeService.setFormulaExecuteStage(
FormulaExecuteStageType.CURRENTLY_CALCULATING_ARRAY_FORMULA
);

this._runtimeService.setCompletedArrayFormulasCount(i + 1);
} else {
this._runtimeService.setFormulaExecuteStage(FormulaExecuteStageType.CURRENTLY_CALCULATING);

this._runtimeService.setCompletedFormulasCount(i + 1);
}

this._executionInProgressListener$.next(this._runtimeService.getRuntimeState());
}

const tree = treeList[i];
const nodeData = tree.nodeData;
const getDirtyData = tree.getDirtyData;
Expand Down Expand Up @@ -267,6 +324,16 @@ export class CalculateFormulaService extends Disposable {
}
}

// clear all pending tasks
pendingTasks.forEach((cancel) => cancel());
pendingTasks = [];

if (treeList.length > 0) {
this._runtimeService.markedAsSuccessfullyExecuted();
} else if (!isArrayFormulaState) {
this._runtimeService.markedAsNoFunctionsExecuted();
}

this._runtimeService.clearReferenceAndNumberformatCache();

return this._runtimeService.getAllRuntimeData();
Expand Down
Loading

0 comments on commit a9d5579

Please sign in to comment.