Skip to content
This repository has been archived by the owner on Mar 5, 2018. It is now read-only.

Test and refactor rendering #40

Merged
merged 3 commits into from
Apr 19, 2017

Conversation

pittst3r
Copy link
Contributor

@pittst3r pittst3r commented Mar 30, 2017

In order to test rendering (which is currently async) we needed to return a promise from scheduleRerender and by extension renderComponent. What do y'all think about this?

cc @chancancode @tomdale

private _initializers: Initializer[] = [];
private _initialized = false;

constructor(options: ApplicationOptions) {
this.rootName = options.rootName;
this.resolver = options.resolver;
this._scheduled = new Promise<void>(resolve => {
this._afterRender = resolve;
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what allows us to get rid of _rendered.

return app.renderComponent('hello-world', containerElement).then(() => {
assert.equal(containerElement.innerHTML, '<h1>Hello Glimmer!</h1>');
});
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests for rendering multiple components, rendering into tags with existing content (with or without nextSibling), etc?

@pittst3r
Copy link
Contributor Author

pittst3r commented Apr 2, 2017

@chancancode Just added a bunch of tests. Let me know how this looks.

@tomdale
Copy link
Contributor

tomdale commented Apr 3, 2017

scheduleRerender is a potentially very hot path, so we need to be careful not to do anything to deopt it.

I'm OK with this returning a promise, but:

  1. Should we have an optimized path that doesn't allocate a Promise? The reality is that this is getting called potentially every rAF and the internals don't require the promise. We should avoid the allocation if we don't actually need it.
  2. We should continue to maintain a Boolean flag, and make the check more explicit, c.f. https://twitter.com/michaelhaeu/status/845003383153025024 and More specific if conditions lead to ~10% faster render. preactjs/preact#610.

@chancancode
Copy link

chancancode commented Apr 3, 2017

@robbiepitts @tomdale

Re: (2) the micro-optimization from Benedikt's slide isn't actually referring to this case (and it likely won't matter here). If we care about that, the right thing to do here is to check for (this._scheduled !== null). Maintaining an parallel boolean field would likely turn out to be more costly (again, I doubt that matters at all – whatever makes the code easy to work with here wins IMO).

We can do (1) though. Something like this would probably work:

function NOOP() {}

class Application {
  private rerender: () => void = NOOP;
  private afterRender: () => void = NOOP;
  private renderPromise: Option<Promise<void>>;

  constructor() {
    // ...

    // This, or maintain the `rendered` flag, etc
    this.renderPromise = new Promise<void>(resolve =>{
      this.afterRender = resolve;
    });
  }

  render() {
    // ...
    this.env.commit();

    let renderResult = result.value;

    this.rerender = () => {
      this.env.begin();
      renderResult.rerender();
      this.env.commit();
      this.didRender();
    };

    this.didRender();
  }

  didRender(): void {
    let { afterRender } = this;

    this.afterRender = NOOP;
    this.renderPromise = null;

    afterRender();
  }

  scheduleRerender(): Promise<void> {
    let { renderPromise } = this;

    if (renderPromise === null) {
      renderPromise = this.renderPromise = new Promise<void>(resolve =>{
        this.afterRender = resolve;
      });

      this._scheduleRerender();
    }

    return renderPromise;
  }

  // internal version used by @tracked, etc
  _scheduleRerender() {
    if (this.renderPromise !== null) {
      requestAnimationFrame(this.rerender);
    }
  }
}

Does that make sense?

@pittst3r
Copy link
Contributor Author

Just updated this per @chancancode's revision. I feel like I'm missing something though. I'm not seeing a path that avoids the Promise allocation. You can call _scheduleRerender once after the initial render, but then after that it's a noop.

@pittst3r pittst3r force-pushed the render-component-refactor branch 2 times, most recently from 46acd0e to f907df6 Compare April 14, 2017 12:28
@pittst3r
Copy link
Contributor Author

My latest commit adds the promise-free path along with a couple boolean flags that seemed necessary to me. Essentially, _scheduleRerender has turned into the old scheduleRerender and the new scheduleRerender wraps it in a promise.

@chancancode
Copy link

Looks good to me! @krisselden might be able to comment on what patterns are good/bad performance-wise, but that could happen async and we don't need to block on that. @tomdale seems good to you?

@tomdale
Copy link
Contributor

tomdale commented Apr 19, 2017

This looks good. My one request is that sooner rather than later we think generally about how we want to expose global rendering/lifecycle events. There are a number of things that are currently internal that people might want for testing, analytics, performance measuring, etc. I'd prefer to think about the problem holistically instead of accreting promise-based APIs for every use case that crops up.

@tomdale tomdale merged commit c085b46 into glimmerjs:master Apr 19, 2017
@pittst3r pittst3r deleted the render-component-refactor branch April 21, 2017 15:48
@pittst3r pittst3r mentioned this pull request Apr 22, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants