diff --git a/app/client/src/workers/Evaluation/handlers/evalTree.ts b/app/client/src/workers/Evaluation/handlers/evalTree.ts index cc16a6e0bc25..f25ff9255aa6 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTree.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTree.ts @@ -1,7 +1,7 @@ import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeTypes"; import type ReplayEntity from "entities/Replay"; import ReplayCanvas from "entities/Replay/ReplayEntity/ReplayCanvas"; -import { isEmpty } from "lodash"; +import { isEmpty, set, get, unset } from "lodash"; import type { DependencyMap, EvalError } from "utils/DynamicBindingUtils"; import { EvalErrorTypes } from "utils/DynamicBindingUtils"; import type { JSUpdate } from "utils/JSPaneUtils"; @@ -35,6 +35,9 @@ import type { CanvasWidgetsReduxState } from "ee/reducers/entityReducers/canvasW import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer"; import type { Attributes } from "instrumentation/types"; import { updateActionsToEvalTree } from "./updateActionData"; +import { create } from "mutative"; +import { klona } from "klona"; +import { klona as klonaJSON } from "klona/json"; // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -192,6 +195,10 @@ export async function evalTree( }); staleMetaIds = dataTreeResponse.staleMetaIds; } else { + dataTreeEvaluator.shouldRunOptimisation = true; + dataTreeEvaluator.setSemiUpdatedPrevTree( + dataTreeEvaluator.getPrevState(), + ); const tree = dataTreeEvaluator.getEvalTree(); // during update cycles update actions to the dataTree directly @@ -244,10 +251,40 @@ export async function evalTree( ), ); - dataTree = makeEntityConfigsAsObjProperties(dataTreeEvaluator.evalTree, { - evalProps: dataTreeEvaluator.evalProps, - }); + dataTree = create( + dataTreeEvaluator.getSemiUpdatedPrevTree() || {}, + (draft: DataTree) => { + for (const fullPropertyPath of evalOrder) { + const value = klonaJSON( + get(dataTreeEvaluator?.evalTree, fullPropertyPath), + ); + + if (value === undefined) { + unset(draft, fullPropertyPath); + } else { + set(draft, fullPropertyPath, value); + } + } + + const evalProps = dataTreeEvaluator?.evalProps; + + if (!evalProps) return; + + for (const [entityName, entityEvalProps] of Object.entries( + evalProps, + )) { + if (!entityEvalProps.__evaluation__) continue; + + set( + draft[entityName as keyof DataTree], + "__evaluation__", + klona({ errors: entityEvalProps.__evaluation__.errors }), + ); + } + }, + ); + dataTreeEvaluator.shouldRunOptimisation = false; evalMetaUpdates = JSON.parse( JSON.stringify(updateResponse.evalMetaUpdates), ); diff --git a/app/client/src/workers/Evaluation/handlers/updateActionData.ts b/app/client/src/workers/Evaluation/handlers/updateActionData.ts index ccb3f6693e69..91ccea46245c 100644 --- a/app/client/src/workers/Evaluation/handlers/updateActionData.ts +++ b/app/client/src/workers/Evaluation/handlers/updateActionData.ts @@ -6,6 +6,8 @@ import DataStore from "../dataStore"; import { EVAL_WORKER_SYNC_ACTION } from "ee/workers/Evaluation/evalWorkerActions"; import type { DataTree } from "entities/DataTree/dataTreeTypes"; import type { UpdateActionProps } from "./types"; +import { create } from "mutative"; +import { klona as klonaJSON } from "klona/json"; export default function (request: EvalWorkerSyncRequest) { const actionsDataToUpdate: UpdateActionProps[] = request.data; @@ -45,22 +47,57 @@ export function updateActionsToEvalTree( ) { if (!actionsToUpdate) return; - for (const actionToUpdate of actionsToUpdate) { - const { dataPath, dataPathRef, entityName } = actionToUpdate; - let { data } = actionToUpdate; + if (!dataTreeEvaluator?.shouldRunOptimisation) { + for (const actionToUpdate of actionsToUpdate) { + const { dataPath, dataPathRef, entityName } = actionToUpdate; + let { data } = actionToUpdate; - if (dataPathRef) { - data = DataStore.getActionData(dataPathRef); - DataStore.deleteActionData(dataPathRef); + if (dataPathRef) { + data = DataStore.getActionData(dataPathRef); + DataStore.deleteActionData(dataPathRef); + } + + // update the evaltree + set(evalTree, `${entityName}.[${dataPath}]`, data); + // Update context + set(self, `${entityName}.[${dataPath}]`, data); + // Update the datastore + const path = `${entityName}.${dataPath}`; + + DataStore.setActionData(path, data); } + } else { + const updatedPrevState = create( + dataTreeEvaluator.getSemiUpdatedPrevTree(), + (draft) => { + for (const actionToUpdate of actionsToUpdate) { + const { dataPath, dataPathRef, entityName } = actionToUpdate; + let { data } = actionToUpdate; + + if (dataPathRef) { + data = DataStore.getActionData(dataPathRef); + DataStore.deleteActionData(dataPathRef); + } + + // update the evaltree + set(evalTree, `${entityName}.[${dataPath}]`, data); + + if (draft) { + set(draft, `${entityName}.[${dataPath}]`, klonaJSON(data)); + } + + // Update context + set(self, `${entityName}.[${dataPath}]`, data); + // Update the datastore + const path = `${entityName}.${dataPath}`; + + DataStore.setActionData(path, data); + } + }, + ); - // update the evaltree - set(evalTree, `${entityName}.[${dataPath}]`, data); - // Update context - set(self, `${entityName}.[${dataPath}]`, data); - // Update the datastore - const path = `${entityName}.${dataPath}`; + dataTreeEvaluator.setSemiUpdatedPrevTree(updatedPrevState); - DataStore.setActionData(path, data); + // Update setSemiUpdatedPrevTree with mutative } } diff --git a/app/client/src/workers/common/DataTreeEvaluator/index.ts b/app/client/src/workers/common/DataTreeEvaluator/index.ts index 2df85afa2537..8255d2f10c57 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/index.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/index.ts @@ -156,6 +156,7 @@ import { WorkerEnv } from "workers/Evaluation/handlers/workerEnv"; import type { WebworkerSpanData, Attributes } from "instrumentation/types"; import type { AffectedJSObjects } from "actions/EvaluationReduxActionTypes"; import type { UpdateActionProps } from "workers/Evaluation/handlers/types"; +import { create } from "mutative"; type SortedDependencies = Array; export interface EvalProps { @@ -194,6 +195,8 @@ export default class DataTreeEvaluator { undefinedEvalValuesMap: Record = {}; prevState = {}; + semiUpdatedPrevState: DataTree | null = null; + // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any setPrevState(state: any) { @@ -216,7 +219,15 @@ export default class DataTreeEvaluator { getEvalTree() { return this.evalTree; } + shouldRunOptimisation = false; + + getSemiUpdatedPrevTree() { + return this.semiUpdatedPrevState; + } + setSemiUpdatedPrevTree(dataTree: DataTree | null) { + this.semiUpdatedPrevState = dataTree; + } getEvalProps() { return this.evalProps; } @@ -791,21 +802,52 @@ export default class DataTreeEvaluator { // Remove anything from the sort order that is not a dynamic leaf since only those need evaluation const evaluationOrder: string[] = []; - for (const fullPath of subTreeSortOrder) { - if (pathsToSkipFromEval.includes(fullPath)) continue; + const semiUpdatedPrevTree = this.getSemiUpdatedPrevTree(); + + if (this.shouldRunOptimisation) { + const updatedSemiUpdatedPrevTree = create( + semiUpdatedPrevTree || {}, + (draft: DataTree) => { + for (const fullPath of subTreeSortOrder) { + if (pathsToSkipFromEval.includes(fullPath)) continue; + + if (!isDynamicLeaf(unEvalTree, fullPath, this.getConfigTree())) + continue; + + const evalPropValue = get(this.evalTree, fullPath); + + evaluationOrder.push(fullPath); + + if (isFunction(evalPropValue)) continue; + + const unEvalPropValue = get(unEvalTree, fullPath); + + // Set all values from unEvalTree to the evalTree to run evaluation for unevaluated values. + set(this.evalTree, fullPath, klona(unEvalPropValue)); + set(draft, fullPath, klona(unEvalPropValue)); + } + }, + ); - if (!isDynamicLeaf(unEvalTree, fullPath, this.getConfigTree())) continue; + this.setSemiUpdatedPrevTree(updatedSemiUpdatedPrevTree); + } else { + for (const fullPath of subTreeSortOrder) { + if (pathsToSkipFromEval.includes(fullPath)) continue; + + if (!isDynamicLeaf(unEvalTree, fullPath, this.getConfigTree())) + continue; - const evalPropValue = get(this.evalTree, fullPath); + const evalPropValue = get(this.evalTree, fullPath); - evaluationOrder.push(fullPath); + evaluationOrder.push(fullPath); - if (isFunction(evalPropValue)) continue; + if (isFunction(evalPropValue)) continue; - const unEvalPropValue = get(unEvalTree, fullPath); + const unEvalPropValue = get(unEvalTree, fullPath); - // Set all values from unEvalTree to the evalTree to run evaluation for unevaluated values. - set(this.evalTree, fullPath, klona(unEvalPropValue)); + // Set all values from unEvalTree to the evalTree to run evaluation for unevaluated values. + set(this.evalTree, fullPath, klona(unEvalPropValue)); + } } return { evaluationOrder }; @@ -831,6 +873,27 @@ export default class DataTreeEvaluator { updateEvalTreeWithJSCollectionState(this.evalTree); + if (this.shouldRunOptimisation) { + const jsCollections = JSObjectCollection.getVariableState(); + const jsCollectionEntries = Object.entries(jsCollections); + + const updatedSemiUpdatedPrevTree = create( + this.getSemiUpdatedPrevTree() || {}, + (draft: DataTree) => { + for (const [jsObjectName, variableState] of jsCollectionEntries) { + if (!draft[jsObjectName]) continue; + + draft[jsObjectName] = Object.assign( + draft[jsObjectName], + klonaJSON(variableState), + ); + } + }, + ); + + this.setSemiUpdatedPrevTree(updatedSemiUpdatedPrevTree); + } + const calculateSortOrderStartTime = performance.now(); const subTreeSortOrder = this.calculateSubTreeSortOrder( updatedValuePaths, @@ -854,6 +917,19 @@ export default class DataTreeEvaluator { evaluationOrder: evaluationOrder, }); + if (this.shouldRunOptimisation) { + const updatedSemiUpdatedPrevTree1 = create( + this.getSemiUpdatedPrevTree() || {}, + (draft: DataTree) => { + removedPaths.forEach((removedPath) => { + unset(draft, removedPath.fullpath); + }); + }, + ); + + this.setSemiUpdatedPrevTree(updatedSemiUpdatedPrevTree1); + } + // Remove any deleted paths from the eval tree removedPaths.forEach((removedPath) => { unset(this.evalTree, removedPath.fullpath); @@ -1796,12 +1872,35 @@ export default class DataTreeEvaluator { // eslint-disable-next-line @typescript-eslint/no-explicit-any differences: Diff[]; }) { + if (differences.length === 0) return; + + // Apply changes to this.evalTree for (const d of differences) { if (!Array.isArray(d.path) || d.path.length === 0) continue; // Null check for typescript // Apply the changes into the evalTree so that it gets the latest changes applyChange(this.evalTree, undefined, d); } + + if (this.shouldRunOptimisation) { + // Update the semi-updated previous state with the same differences + const semiUpdatedPrevState = this.getSemiUpdatedPrevTree(); + + if (semiUpdatedPrevState) { + const updatedSemiPrevState = create( + semiUpdatedPrevState || {}, + (draft: DataTree) => { + for (const d of differences) { + if (!Array.isArray(d.path) || d.path.length === 0) continue; + + applyChange(draft, undefined, klona(d)); + } + }, + ); + + this.setSemiUpdatedPrevTree(updatedSemiPrevState); + } + } } calculateSubTreeSortOrder(