properly handle concurrent rendering passes

This commit is contained in:
Géry Debongnie
2019-02-07 12:46:30 +01:00
parent f648f47197
commit 66da26f92f
3 changed files with 168 additions and 47 deletions
+39 -18
View File
@@ -28,6 +28,10 @@ interface Meta<T extends WEnv> {
// children mapping: from templateID to widgetID
// should it be a map number => Widget?
cmap: { [key: number]: number };
renderId: number;
renderProps: any;
renderPromise: Promise<VNode> | null;
}
const patch = init([sdListeners, sdAttrs]);
@@ -92,7 +96,10 @@ export class Component<
isDestroyed: false,
parent: p,
children: {},
cmap: {}
cmap: {},
renderId: 1,
renderPromise: null,
renderProps: props
};
}
@@ -203,28 +210,44 @@ export class Component<
if (this.__widget__.isDestroyed) {
return;
}
const vnode = await this._render();
this._patch(vnode);
const renderVDom = this._render();
const renderId = this.__widget__.renderId;
const vnode = await renderVDom;
if (renderId === this.__widget__.renderId) {
// we only update the vnode and the actual DOM if no other rendering
// occurred between now and when the render method was initially called.
this._patch(vnode);
}
}
private _patch(vnode) {
this.__widget__.renderPromise = null;
this.__widget__.vnode = patch(
this.__widget__.vnode || document.createElement(vnode.sel!),
vnode
);
}
private async _start(): Promise<VNode> {
await this.willStart();
if (!this.__widget__.isDestroyed) {
this.__widget__.renderProps = this.props;
this.__widget__.renderPromise = this.willStart().then(() => {
if (this.__widget__.isDestroyed) {
return Promise.resolve(this.env.qweb.render("default"));
}
this.__widget__.isStarted = true;
}
if (this.inlineTemplate) {
this.env.qweb.addTemplate(this.inlineTemplate, this.inlineTemplate, true);
}
return this._render();
if (this.inlineTemplate) {
this.env.qweb.addTemplate(
this.inlineTemplate,
this.inlineTemplate,
true
);
}
return this._render();
});
return this.__widget__.renderPromise;
}
async _render(): Promise<VNode> {
this.__widget__.renderId++;
const promises: Promise<void>[] = [];
const template = this.inlineTemplate || this.template;
let vnode = this.env.qweb.render(template, this, { promises });
@@ -235,7 +258,9 @@ export class Component<
// parent widget. With this, we make sure that the parent widget will be
// able to patch itself properly after
vnode.key = this.__widget__.id;
return Promise.all(promises).then(() => vnode);
this.__widget__.renderProps = this.props;
this.__widget__.renderPromise = Promise.all(promises).then(() => vnode);
return this.__widget__.renderPromise;
}
/**
@@ -274,12 +299,8 @@ export class Component<
}
}
export class PureComponent<T extends WEnv, Props, State> extends Component<
T,
Props,
State
> {
shouldUpdate(nextProps: Props): boolean {
export class PureComponent<T extends WEnv, P, S> extends Component<T, P, S> {
shouldUpdate(nextProps: P): boolean {
for (let k in nextProps) {
if (nextProps[k] !== this.props[k]) {
return true;
@@ -287,7 +308,7 @@ export class PureComponent<T extends WEnv, Props, State> extends Component<
}
return false;
}
async updateState(nextState: Partial<State>) {
async updateState(nextState: Partial<S>) {
for (let k in nextState) {
if (nextState[k] !== this.state[k]) {
return super.updateState(nextState);
+81 -28
View File
@@ -799,43 +799,96 @@ const widgetDirective: Directive = {
ctx.addLine(
`let w${widgetID} = ${templateID} in context.__widget__.cmap ? context.__widget__.children[context.__widget__.cmap[${templateID}]] : false;`
);
ctx.addLine(`if (w${widgetID}) {`);
ctx.addLine(`let props${widgetID} = ${props};`);
ctx.addLine(`let isNew${widgetID} = !w${widgetID};`);
// check if we can reuse current rendering promise
ctx.addLine(`if (w${widgetID} && w${widgetID}.__widget__.renderPromise) {`);
ctx.indent();
ctx.addLine(`if (w${widgetID}.__widget__.isStarted) {`);
ctx.indent();
ctx.addLine(
`def${defID} = w${widgetID}.updateProps(${props}).then(()=>{let vnode=h(w${widgetID}.__widget__.vnode.sel, {key: ${templateID}});vnode.elm=w${widgetID}.el;c${
`if (props${widgetID} === w${widgetID}.__widget__.renderProps) {`
);
ctx.indent();
ctx.addLine(`def${defID} = w${widgetID}.__widget__.renderPromise;`);
ctx.dedent();
ctx.addLine(`} else {`);
ctx.indent();
ctx.addLine(`def${defID} = w${widgetID}.updateProps(props${widgetID});`);
ctx.dedent();
ctx.addLine(`}`);
ctx.dedent();
ctx.addLine(`} else {`); // not started
ctx.indent();
ctx.addLine(
`if (props${widgetID} === w${widgetID}.__widget__.renderProps) {`
);
ctx.indent();
ctx.addLine(`def${defID} = w${widgetID}.__widget__.renderPromise;`);
ctx.addLine(`isNew${widgetID} = true`);
ctx.dedent();
ctx.addLine(`} else {`);
ctx.indent();
ctx.addLine(`w${widgetID}.destroy();`);
ctx.addLine(`w${widgetID} = false`);
ctx.dedent();
ctx.addLine(`}`);
ctx.dedent();
ctx.addLine(`}`);
ctx.dedent();
ctx.addLine(`}`);
ctx.addLine(`if (!def${defID}) {`);
ctx.indent();
ctx.addLine(`if (w${widgetID}) {`);
ctx.indent();
ctx.addLine(`def${defID} = w${widgetID}.updateProps(props${widgetID});`);
ctx.dedent();
ctx.addLine(`} else {`);
ctx.indent();
ctx.addLine(
`w${widgetID} = new context.widgets['${value}'](owner, props${widgetID});`
);
ctx.addLine(
`context.__widget__.cmap[${templateID}] = w${widgetID}.__widget__.id;`
);
for (let [event, method] of events) {
ctx.addLine(`w${widgetID}.on('${event}', owner, owner['${method}'])`);
}
let ref = node.getAttribute("t-ref");
if (ref) {
ctx.addLine(`context.refs['${ref}'] = w${widgetID};`);
}
ctx.addLine(`def${defID} = w${widgetID}._start();`);
ctx.dedent();
ctx.addLine(`}`);
ctx.dedent();
ctx.addLine(`}`);
ctx.addLine(`if (isNew${widgetID}) {`);
ctx.indent();
ctx.addLine(
`def${defID} = def${defID}.then(vnode=>{let pvnode=h(vnode.sel, {key: ${templateID}});c${
ctx.parentNode
}[_${dummyID}_index]=pvnode;pvnode.data.hook = {insert(vn){let nvn=w${widgetID}._mount(vnode, vn.elm);pvnode.elm=nvn.elm},remove(){w${widgetID}.${
keepAlive ? "detach" : "destroy"
}()}}});`
);
ctx.dedent();
ctx.addLine(`} else {`);
ctx.indent();
ctx.addLine(
`def${defID} = def${defID}.then(()=>{let vnode=h(w${widgetID}.__widget__.vnode.sel, {key: ${templateID}});vnode.elm=w${widgetID}.el;c${
ctx.parentNode
}[_${dummyID}_index]=vnode;vnode.data.hook = {insert(a){a.elm.parentNode.replaceChild(w${widgetID}.el,a.elm);a.elm=w${widgetID}.el;w${widgetID}.__mount();},remove(){w${widgetID}.${
keepAlive ? "detach" : "destroy"
}()}}});`
);
ctx.dedent();
ctx.addLine("} else {");
ctx.indent();
ctx.addLine(`}`);
ctx.addLine(
`let _${widgetID} = new context.widgets['${value}'](owner, ${props});`
);
ctx.addLine(
`context.__widget__.cmap[${templateID}] = _${widgetID}.__widget__.id;`
);
for (let [event, method] of events) {
ctx.addLine(`_${widgetID}.on('${event}', owner, owner['${method}'])`);
}
ctx.addLine(
`def${defID} = _${widgetID}._start().then(() => _${widgetID}._render()).then(vnode=>{let pvnode=h(vnode.sel, {key: ${templateID}});c${
ctx.parentNode
}[_${dummyID}_index]=pvnode;pvnode.data.hook = {insert(vn){let nvn=_${widgetID}._mount(vnode, vn.elm);pvnode.elm=nvn.elm},remove(){_${widgetID}.${
keepAlive ? "detach" : "destroy"
}()}}});`
);
let ref = node.getAttribute("t-ref");
if (ref) {
ctx.addLine(`context.refs['${ref}'] = _${widgetID};`);
}
ctx.dedent();
ctx.addLine("}");
ctx.addLine(`extra.promises.push(def${defID});`);
return true;