Optimize template renderer (#1585)
* Add renderMulti * Batch template rendering * Update tests
This commit is contained in:
parent
28fa3fa795
commit
dabda86259
@ -24,6 +24,8 @@ class AnkiNoteBuilder {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this._markerPattern = AnkiUtil.cloneFieldMarkerPattern(true);
|
this._markerPattern = AnkiUtil.cloneFieldMarkerPattern(true);
|
||||||
this._templateRenderer = new TemplateRendererProxy();
|
this._templateRenderer = new TemplateRendererProxy();
|
||||||
|
this._batchedRequests = [];
|
||||||
|
this._batchedRequestsQueued = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createNote({
|
async createNote({
|
||||||
@ -113,7 +115,7 @@ class AnkiNoteBuilder {
|
|||||||
async _formatField(field, commonData, template, errors=null) {
|
async _formatField(field, commonData, template, errors=null) {
|
||||||
return await this._stringReplaceAsync(field, this._markerPattern, async (g0, marker) => {
|
return await this._stringReplaceAsync(field, this._markerPattern, async (g0, marker) => {
|
||||||
try {
|
try {
|
||||||
return await this._renderTemplate(template, marker, commonData);
|
return await this._renderTemplateBatched(template, commonData, marker);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (Array.isArray(errors)) {
|
if (Array.isArray(errors)) {
|
||||||
const error = new Error(`Template render error for {${marker}}`);
|
const error = new Error(`Template render error for {${marker}}`);
|
||||||
@ -143,4 +145,88 @@ class AnkiNoteBuilder {
|
|||||||
async _renderTemplate(template, marker, commonData) {
|
async _renderTemplate(template, marker, commonData) {
|
||||||
return await this._templateRenderer.render(template, {marker, commonData}, 'ankiNote');
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ class TemplateRendererFrameApi {
|
|||||||
this._templateRenderer = templateRenderer;
|
this._templateRenderer = templateRenderer;
|
||||||
this._windowMessageHandlers = new Map([
|
this._windowMessageHandlers = new Map([
|
||||||
['render', {async: false, handler: this._onRender.bind(this)}],
|
['render', {async: false, handler: this._onRender.bind(this)}],
|
||||||
|
['renderMulti', {async: false, handler: this._onRenderMulti.bind(this)}],
|
||||||
['getModifiedData', {async: false, handler: this._onGetModifiedData.bind(this)}]
|
['getModifiedData', {async: false, handler: this._onGetModifiedData.bind(this)}]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -58,6 +59,10 @@ class TemplateRendererFrameApi {
|
|||||||
return this._templateRenderer.render(template, data, type);
|
return this._templateRenderer.render(template, data, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onRenderMulti({items}) {
|
||||||
|
return this._serializeMulti(this._templateRenderer.renderMulti(items));
|
||||||
|
}
|
||||||
|
|
||||||
_onGetModifiedData({data, type}) {
|
_onGetModifiedData({data, type}) {
|
||||||
const result = this._templateRenderer.getModifiedData(data, type);
|
const result = this._templateRenderer.getModifiedData(data, type);
|
||||||
return this._clone(result);
|
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) {
|
_clone(value) {
|
||||||
return JSON.parse(JSON.stringify(value));
|
return JSON.parse(JSON.stringify(value));
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@
|
|||||||
const templateRenderer = new TemplateRenderer(japaneseUtil);
|
const templateRenderer = new TemplateRenderer(japaneseUtil);
|
||||||
const ankiNoteDataCreator = new AnkiNoteDataCreator(japaneseUtil);
|
const ankiNoteDataCreator = new AnkiNoteDataCreator(japaneseUtil);
|
||||||
templateRenderer.registerDataType('ankiNote', {
|
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);
|
const templateRendererFrameApi = new TemplateRendererFrameApi(templateRenderer);
|
||||||
templateRendererFrameApi.prepare();
|
templateRendererFrameApi.prepare();
|
||||||
|
@ -30,6 +30,11 @@ class TemplateRendererProxy {
|
|||||||
return await this._invoke('render', {template, data, type});
|
return await this._invoke('render', {template, data, type});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async renderMulti(items) {
|
||||||
|
await this._prepareFrame();
|
||||||
|
return await this._invoke('renderMulti', {items});
|
||||||
|
}
|
||||||
|
|
||||||
async getModifiedData(data, type) {
|
async getModifiedData(data, type) {
|
||||||
await this._prepareFrame();
|
await this._prepareFrame();
|
||||||
return await this._invoke('getModifiedData', {data, type});
|
return await this._invoke('getModifiedData', {data, type});
|
||||||
|
@ -29,18 +29,39 @@ class TemplateRenderer {
|
|||||||
this._dataTypes = new Map();
|
this._dataTypes = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerDataType(name, {modifier=null}) {
|
registerDataType(name, {modifier=null, composeData=null}) {
|
||||||
this._dataTypes.set(name, {modifier});
|
this._dataTypes.set(name, {modifier, composeData});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(template, data, type) {
|
render(template, data, type) {
|
||||||
const instance = this._getTemplateInstance(template);
|
const instance = this._getTemplateInstance(template);
|
||||||
data = this._getModifiedData(data, type);
|
data = this._getModifiedData(data, void 0, type);
|
||||||
return this._renderTemplate(instance, data);
|
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) {
|
getModifiedData(data, type) {
|
||||||
return this._getModifiedData(data, type);
|
return this._getModifiedData(data, void 0, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
@ -70,10 +91,16 @@ class TemplateRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getModifiedData(data, type) {
|
_getModifiedData(data, commonData, type) {
|
||||||
if (typeof type === 'string') {
|
if (typeof type === 'string') {
|
||||||
const typeInfo = this._dataTypes.get(type);
|
const typeInfo = this._dataTypes.get(type);
|
||||||
if (typeof typeInfo !== 'undefined') {
|
if (typeof typeInfo !== 'undefined') {
|
||||||
|
if (typeof commonData !== 'undefined') {
|
||||||
|
const {composeData} = typeInfo;
|
||||||
|
if (typeof composeData === 'function') {
|
||||||
|
data = composeData(data, commonData);
|
||||||
|
}
|
||||||
|
}
|
||||||
const {modifier} = typeInfo;
|
const {modifier} = typeInfo;
|
||||||
if (typeof modifier === 'function') {
|
if (typeof modifier === 'function') {
|
||||||
data = modifier(data);
|
data = modifier(data);
|
||||||
|
@ -55,12 +55,28 @@ async function createVM() {
|
|||||||
const japaneseUtil = new JapaneseUtil(null);
|
const japaneseUtil = new JapaneseUtil(null);
|
||||||
this._templateRenderer = new TemplateRenderer(japaneseUtil);
|
this._templateRenderer = new TemplateRenderer(japaneseUtil);
|
||||||
this._templateRenderer.registerDataType('ankiNote', {
|
this._templateRenderer.registerDataType('ankiNote', {
|
||||||
modifier: ({marker, commonData}) => ankiNoteDataCreator.create(marker, commonData)
|
modifier: ({marker, commonData}) => ankiNoteDataCreator.create(marker, commonData),
|
||||||
|
composeData: (marker, commonData) => ({marker, commonData})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async render(template, data, type) {
|
async render(template, data, type) {
|
||||||
return await this._templateRenderer.render(template, data, type);
|
return this._templateRenderer.render(template, data, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
async renderMulti(items) {
|
||||||
|
return this._serializeMulti(this._templateRenderer.renderMulti(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
_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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vm.set({TemplateRendererProxy});
|
vm.set({TemplateRendererProxy});
|
||||||
|
Loading…
Reference in New Issue
Block a user