aboutsummaryrefslogtreecommitdiff
path: root/ext/js
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2021-04-02 19:09:21 -0400
committerGitHub <noreply@github.com>2021-04-02 19:09:21 -0400
commitdabda86259d78ee66125ebf2cff15a7619e0b5da (patch)
tree721043546abc01d5c5a2d17f2f2abd8e34ce2a53 /ext/js
parent28fa3fa795d564135940e8aff52b987a5960f15c (diff)
Optimize template renderer (#1585)
* Add renderMulti * Batch template rendering * Update tests
Diffstat (limited to 'ext/js')
-rw-r--r--ext/js/data/anki-note-builder.js88
-rw-r--r--ext/js/templates/template-renderer-frame-api.js16
-rw-r--r--ext/js/templates/template-renderer-frame-main.js3
-rw-r--r--ext/js/templates/template-renderer-proxy.js5
-rw-r--r--ext/js/templates/template-renderer.js37
5 files changed, 142 insertions, 7 deletions
diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js
index 38f1eb16..b84b2eda 100644
--- a/ext/js/data/anki-note-builder.js
+++ b/ext/js/data/anki-note-builder.js
@@ -24,6 +24,8 @@ class AnkiNoteBuilder {
constructor() {
this._markerPattern = AnkiUtil.cloneFieldMarkerPattern(true);
this._templateRenderer = new TemplateRendererProxy();
+ this._batchedRequests = [];
+ this._batchedRequestsQueued = false;
}
async createNote({
@@ -113,7 +115,7 @@ class AnkiNoteBuilder {
async _formatField(field, commonData, template, errors=null) {
return await this._stringReplaceAsync(field, this._markerPattern, async (g0, marker) => {
try {
- return await this._renderTemplate(template, marker, commonData);
+ return await this._renderTemplateBatched(template, commonData, marker);
} catch (e) {
if (Array.isArray(errors)) {
const error = new Error(`Template render error for {${marker}}`);
@@ -143,4 +145,88 @@ class AnkiNoteBuilder {
async _renderTemplate(template, marker, commonData) {
return await this._templateRenderer.render(template, {marker, commonData}, 'ankiNote');
}
+
+ _getBatchedTemplateGroup(template) {
+ for (const item of this._batchedRequests) {
+ if (item.template === template) {
+ return item;
+ }
+ }
+
+ const result = {template, commonDataRequestsMap: new Map()};
+ this._batchedRequests.push(result);
+ return result;
+ }
+
+ _renderTemplateBatched(template, commonData, marker) {
+ const {promise, resolve, reject} = deferPromise();
+ const {commonDataRequestsMap} = this._getBatchedTemplateGroup(template);
+ let requests = commonDataRequestsMap.get(commonData);
+ if (typeof requests === 'undefined') {
+ requests = [];
+ commonDataRequestsMap.set(commonData, requests);
+ }
+ requests.push({resolve, reject, marker});
+ this._runBatchedRequestsDelayed();
+ return promise;
+ }
+
+ _runBatchedRequestsDelayed() {
+ if (this._batchedRequestsQueued) { return; }
+ this._batchedRequestsQueued = true;
+ Promise.resolve().then(() => {
+ this._batchedRequestsQueued = false;
+ this._runBatchedRequests();
+ });
+ }
+
+ _runBatchedRequests() {
+ if (this._batchedRequests.length === 0) { return; }
+
+ const allRequests = [];
+ const items = [];
+ for (const {template, commonDataRequestsMap} of this._batchedRequests) {
+ const templateItems = [];
+ for (const [commonData, requests] of commonDataRequestsMap.entries()) {
+ const datas = [];
+ for (const {marker} of requests) {
+ datas.push(marker);
+ }
+ allRequests.push(...requests);
+ templateItems.push({type: 'ankiNote', commonData, datas});
+ }
+ items.push({template, templateItems});
+ }
+
+ this._batchedRequests.length = 0;
+
+ this._resolveBatchedRequests(items, allRequests);
+ }
+
+ async _resolveBatchedRequests(items, requests) {
+ let responses;
+ try {
+ responses = await this._templateRenderer.renderMulti(items);
+ } catch (e) {
+ for (const {reject} of requests) {
+ reject(e);
+ }
+ return;
+ }
+
+ for (let i = 0, ii = requests.length; i < ii; ++i) {
+ const request = requests[i];
+ try {
+ const response = responses[i];
+ const {error} = response;
+ if (typeof error !== 'undefined') {
+ throw deserializeError(error);
+ } else {
+ request.resolve(response.result);
+ }
+ } catch (e) {
+ request.reject(e);
+ }
+ }
+ }
}
diff --git a/ext/js/templates/template-renderer-frame-api.js b/ext/js/templates/template-renderer-frame-api.js
index 104e357b..dd6be517 100644
--- a/ext/js/templates/template-renderer-frame-api.js
+++ b/ext/js/templates/template-renderer-frame-api.js
@@ -20,6 +20,7 @@ class TemplateRendererFrameApi {
this._templateRenderer = templateRenderer;
this._windowMessageHandlers = new Map([
['render', {async: false, handler: this._onRender.bind(this)}],
+ ['renderMulti', {async: false, handler: this._onRenderMulti.bind(this)}],
['getModifiedData', {async: false, handler: this._onGetModifiedData.bind(this)}]
]);
}
@@ -58,6 +59,10 @@ class TemplateRendererFrameApi {
return this._templateRenderer.render(template, data, type);
}
+ _onRenderMulti({items}) {
+ return this._serializeMulti(this._templateRenderer.renderMulti(items));
+ }
+
_onGetModifiedData({data, type}) {
const result = this._templateRenderer.getModifiedData(data, type);
return this._clone(result);
@@ -82,6 +87,17 @@ class TemplateRendererFrameApi {
};
}
+ _serializeMulti(array) {
+ for (let i = 0, ii = array.length; i < ii; ++i) {
+ const value = array[i];
+ const {error} = value;
+ if (typeof error !== 'undefined') {
+ value.error = this._serializeError(error);
+ }
+ }
+ return array;
+ }
+
_clone(value) {
return JSON.parse(JSON.stringify(value));
}
diff --git a/ext/js/templates/template-renderer-frame-main.js b/ext/js/templates/template-renderer-frame-main.js
index 40ecf308..3d61295d 100644
--- a/ext/js/templates/template-renderer-frame-main.js
+++ b/ext/js/templates/template-renderer-frame-main.js
@@ -27,7 +27,8 @@
const templateRenderer = new TemplateRenderer(japaneseUtil);
const ankiNoteDataCreator = new AnkiNoteDataCreator(japaneseUtil);
templateRenderer.registerDataType('ankiNote', {
- modifier: ({marker, commonData}) => ankiNoteDataCreator.create(marker, commonData)
+ modifier: ({marker, commonData}) => ankiNoteDataCreator.create(marker, commonData),
+ composeData: (marker, commonData) => ({marker, commonData})
});
const templateRendererFrameApi = new TemplateRendererFrameApi(templateRenderer);
templateRendererFrameApi.prepare();
diff --git a/ext/js/templates/template-renderer-proxy.js b/ext/js/templates/template-renderer-proxy.js
index aba45e6c..f35239cb 100644
--- a/ext/js/templates/template-renderer-proxy.js
+++ b/ext/js/templates/template-renderer-proxy.js
@@ -30,6 +30,11 @@ class TemplateRendererProxy {
return await this._invoke('render', {template, data, type});
}
+ async renderMulti(items) {
+ await this._prepareFrame();
+ return await this._invoke('renderMulti', {items});
+ }
+
async getModifiedData(data, type) {
await this._prepareFrame();
return await this._invoke('getModifiedData', {data, type});
diff --git a/ext/js/templates/template-renderer.js b/ext/js/templates/template-renderer.js
index 5441528c..ed9cc41c 100644
--- a/ext/js/templates/template-renderer.js
+++ b/ext/js/templates/template-renderer.js
@@ -29,18 +29,39 @@ class TemplateRenderer {
this._dataTypes = new Map();
}
- registerDataType(name, {modifier=null}) {
- this._dataTypes.set(name, {modifier});
+ registerDataType(name, {modifier=null, composeData=null}) {
+ this._dataTypes.set(name, {modifier, composeData});
}
render(template, data, type) {
const instance = this._getTemplateInstance(template);
- data = this._getModifiedData(data, type);
+ data = this._getModifiedData(data, void 0, type);
return this._renderTemplate(instance, data);
}
+ renderMulti(items) {
+ const results = [];
+ for (const {template, templateItems} of items) {
+ const instance = this._getTemplateInstance(template);
+ for (const {type, commonData, datas} of templateItems) {
+ for (let data of datas) {
+ let result;
+ try {
+ data = this._getModifiedData(data, commonData, type);
+ result = this._renderTemplate(instance, data);
+ result = {result};
+ } catch (error) {
+ result = {error};
+ }
+ results.push(result);
+ }
+ }
+ }
+ return results;
+ }
+
getModifiedData(data, type) {
- return this._getModifiedData(data, type);
+ return this._getModifiedData(data, void 0, type);
}
// Private
@@ -70,10 +91,16 @@ class TemplateRenderer {
}
}
- _getModifiedData(data, type) {
+ _getModifiedData(data, commonData, type) {
if (typeof type === 'string') {
const typeInfo = this._dataTypes.get(type);
if (typeof typeInfo !== 'undefined') {
+ if (typeof commonData !== 'undefined') {
+ const {composeData} = typeInfo;
+ if (typeof composeData === 'function') {
+ data = composeData(data, commonData);
+ }
+ }
const {modifier} = typeInfo;
if (typeof modifier === 'function') {
data = modifier(data);