Skip to content

Commit

Permalink
improved performance of templates by introducing caching compiled exp…
Browse files Browse the repository at this point in the history
…ressions for template controls
  • Loading branch information
ishubin committed Jan 20, 2025
1 parent a9e175d commit dbda4dc
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 37 deletions.
1 change: 0 additions & 1 deletion src/ui/components/editor/EditBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,6 @@ import StoreUtils from '../../store/StoreUtils';
import StrokePattern from './items/StrokePattern';
import myMath from '../../myMath';
import { itemCompleteTransform, worldPointOnItem } from '../../scheme/ItemMath';
import { compileItemTemplate } from './items/ItemTemplate';
import EditorEventBus from './EditorEventBus';
import utils from '../../utils';
import { jsonDiff } from '../../json-differ';
Expand Down
83 changes: 55 additions & 28 deletions src/ui/components/editor/items/ItemTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ function enrichPanelItem(item) {
* @param {Item} templateRootItem
* @param {Object} data
* @param {Array<String>} selectedItemIds
* @param {Map<String, Function>} cachedCompiledExpressions
*/
function buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, data, selectedItemIds) {
function buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, data, selectedItemIds, cachedCompiledExpressions) {
// cloning selected items to make sure that the script cannot mutate items
const extraData = {
selectedItemIds: new List(...selectedItemIds),
Expand All @@ -73,9 +74,14 @@ function buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, d
}
let click = null;
if (panel.click) {
const clickCallback = compileTemplateExpressions(initBlock.concat([panel.click]), {
context: new TemplateContext(ContextPhases.EVENT, 'panel-click', panel.id)
});
const clickExpression = initBlock.concat([panel.click]).join('\n');

let clickCallback = cachedCompiledExpressions.get(clickExpression);
if (!clickCallback) {
clickCallback = compileTemplateExpressions(clickExpression);
cachedCompiledExpressions.set(clickExpression, clickCallback);
}

click = (panelItem) => {
// panel click callback is supposed to return the object that contains the top-level scope fields
// This can be used in order to update template args in the template root item
Expand All @@ -90,7 +96,8 @@ function buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, d
width: templateRootItem.area.w,
height: templateRootItem.area.h,
...extraData,
panelItem
panelItem,
context: new TemplateContext(ContextPhases.EVENT, 'panel-click', panel.id)
};
return clickCallback(clickData);
};
Expand Down Expand Up @@ -167,6 +174,19 @@ export function compileItemTemplate(editorId, template, templateRef) {
defaultArgs[argName] = arg;
});

const cachedCompiledExpressions = new Map();

const buildTemplateExpression = (expressionScript) => {
let cachedExpression = cachedCompiledExpressions.get(expressionScript);
if (cachedExpression) {
return cachedExpression;
}

const compiledExpression = compileTemplateExpressions(expressionScript);
cachedCompiledExpressions.set(expressionScript, compiledExpression);
return compiledExpression;
};

return {
name : template.name,
description: template.description,
Expand Down Expand Up @@ -246,32 +266,39 @@ export function compileItemTemplate(editorId, template, templateRef) {
},

buildEditor: (templateRootItem, args, width, height, selectedItemIds) => {
return buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, {...args, width, height}, selectedItemIds);
return buildEditor(editorId, editorJSONBuilder, initBlock, templateRootItem, {...args, width, height}, selectedItemIds, cachedCompiledExpressions);
},

buildControls: (args, width, height) => compiledControlBuilder({
...args, width, height,
context: new TemplateContext(ContextPhases.EVENT, 'control', '')
}).controls.map(control => {
const controlExpressions = [].concat(initBlock).concat(toExpressionBlock(control.click));
const clickExecutor = compileTemplateExpressions(controlExpressions, {
...args, width, height,
context: new TemplateContext(ContextPhases.EVENT, 'control', control.id)
});
return {
...control,

/**
* @param {Item} item
* @returns {Object} updated data object which can be used to update the template args.
* Keep in mind that this object contains not only template args,
* but everything that was declared in the global scope of the template script
*/
click: (item) => {
return clickExecutor({control, ...createTemplateFunctions(editorId, item)});
buildControls: (args, width, height) => {
return compiledControlBuilder({
...args, width, height,
context: new TemplateContext(ContextPhases.EVENT, 'control', '')
}).controls.map(control => {

const controlExpressions = [].concat(initBlock).concat(toExpressionBlock(control.click));
const fullScript = controlExpressions.join('\n');

const clickExecutor = buildTemplateExpression(fullScript);
return {
...control,

/**
* @param {Item} item
* @returns {Object} updated data object which can be used to update the template args.
* Keep in mind that this object contains not only template args,
* but everything that was declared in the global scope of the template script
*/
click: (item) => {
return clickExecutor({
control,
...createTemplateFunctions(editorId, item),
...args, width, height,
context: new TemplateContext(ContextPhases.EVENT, 'control', control.id)
});
}
}
}
}),
});
},

getDefaultArgs() {
const args = {};
Expand Down
11 changes: 3 additions & 8 deletions src/ui/templater/templater.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,12 @@ export function processJSONTemplate(obj, data) {
/**
* This function is used when user clicks on template controls.
* It executes init and control expressions and returns the updated template argument values
* @param {Array<String>} expressions - an array of strings which represent template expressions
* @param {String} expression - a template expression in SchemioScript
* @param {Object} data - an object with initial arguments
* @returns {function(Object|undefined): Object} - a function that takes extra data object as an argument, that should be added to the scope and, when invoked, will execute the expressions and will return the updated data with arguments
*/
export function compileTemplateExpressions(expressions, data) {
if (!Array.isArray(expressions)) {
expressions = [expressions];
}

const fullScript = expressions.join('\n');
const expressionNode = parseExpression(fullScript);
export function compileTemplateExpressions(expression, data = {}) {
const expressionNode = parseExpression(expression);

return (extraData) => {
const scope = new Scope({...data, ...(extraData || {})});
Expand Down

0 comments on commit dbda4dc

Please sign in to comment.