When the data-src attribute of a sib-display changes, we re-render the component.
When this data-src changes multiple times in a very small time, each populate wait for the previous one to be finished before starting.
Ideally, all the previous populate should be cancelled and only the last one should be executed.
Designs
Child items
...
Show closed items
Linked items
0
Link issues together to show that they're related.
Learn more.
For notice, this is a main performance issue on Sib app when you click on each menus like @alex do, framework await every render even those that are not needed anymore. Each click adds a minimum of 300ms to the pile which can grows up pretty fast.
The display of the users this way seems very long.
Moreover, each time the data-src of the first sib-display changes, the whole list is re-rendered. If it changes twice very quickly, the list renders for the first source entirely before doing to the next one.
By looking more in details, it seems that the counter we were looking at was not representative of the render, because it was not related to the members list.
It's not clear for me if there is a difference, but I think this 2 syntax can display the same thing but the render time is different:
when a circle link is clicked in the menu, the router binds the resource in all the components of the circle page. Each of these components has a new data-src, and re-renders itself (long process because a lot of users to show)
when 10 links a clicked quickly, 10 renders happens at the same time, but 9 of them are useless (actually, the rendering div is not even in the DOM anymore)
2. Proposed solution
The StoreMixin gives his current data-src at the rendering method, so it can always be aware of which render it's running.
At different steps of the rendering process, we check if the data-src we are currently rendering is the same than the data-src actually in the DOM. If not, it means that we are rendering an old version of the component, and we need to stop it now because it became useless.
Current situation
I implemented this for the sib-display, and it seems to be better. At least, I don't see an increasing time freeze when switching tab.
If this solution with a list is almost ready, the problem is that we use multiple widgets in the circle pages, which works almost the same way. I see 3 solutions for this:
Adapt the proposed solution to work with multiple widgets too
Transform every big multiple widgets in sib-display with nested fields (needs some styling update). Example:
<!-- instead of this --><sib-displaydata-src="https://api.alpha.happy-dev.fr/circles/13/"fields="members"multiple-memberswidget-members="test-widget"></sib-display><sib-widgetname="test-widget"><template><sib-displaydata-src="${await value.user}"fields="name"></sib-display></template></sib-widget><!-- do this --><sib-displaydata-src="https://api.alpha.happy-dev.fr/circles/13/"nested-field="members"fields="user.name"></sib-display>
Or find a more generic solution to know if a widget or a component is still in the DOM and needs to finish its rendering or not
Probably a mistake but I see no nested-fields in your example :-)
You're right, wrong copy-paste. I updated the code.
How are you checking that ? Do you have a code sample available ?
// In storeMixin...attributes:{dataSrc:{callback:asyncfunction (value:string){...this.populate(value);}}},outdated(value:String){if (value!==this.dataSrc)console.log('outdated, stop rendering');returnvalue!==this.dataSrc;}...// In listMixin...asyncpopulate(value){...if (this.outdated(value))return;}
I don't think it covers everything. It will allow to launch a new populate as soon as something changes. But it won't be able to stop an already running populate
Yes it will. But I think, even if each rendering is not blocking, there will probably still be the performance issue due to the fact that multiple rendering are running at the same time
I think most of the rewriting here would happen in the populate and post-processors, so it has no impact on the storeMixin / eventHandlers.
But you're right, we should maybe see #524 (closed) first
Current status: In the sib-app, when you click on a circle, it renders all the components and widgets in the circle informations and edition pages, where there is a lot.
This process takes time, and when you switch circles quickly, it seems that the framework waits to finish the rendering before moving to the next one.
What I try to do in the framework, is to stop a render if it's not needed anymore. For this, I already did:
When a widget is not in the DOM, it has probably been removed, so we do not render it: 58e51059
When a sib-display has different data-src in a short period of time, it needs to be sure to re-render only for the last one: 4cfc49b8
This allows us to be a bit faster when you click on a circle which has not been loaded before -> a new data-src is set, the StoreMixin makes a request, and when it returns, it realizes there is a new data-src so it stops rendering.
However, when the data is already in the cache, the request to fetch data resolves immediately, so the render method is called, and cannot be stopped. This needs to be improved
@matthieu Your good communication honours your. The whole team should take you as a source of inspiration ;-)
I've always thought it was really dumb to render the WHOLE view when accessing it. On the SIB app, when you access a Circle, you land on the "chat" sub view, and in 99% of cases, you won't even access "information" and "edition" sub views.
Why do we even render them then?
We should render a sub view when it is accessed only, no?
@jbpasquier@alexbourlier if it's easy to set up, you can make some tests with the branch bugfix/594-performance-improvements of sib-core and tell me what you think
I'm wondering what is the cost of document.contains, and if it is worth it.
Good question. I tried to estimate time with the sib-app. On my computed, it is 95% of the time under 1ms, 5% between 1 and 2ms. I think it is still cheaper than re-rendering the widget
It's not easy because the widget can be in a parent but the parent not in the DOM. As we can nest widgets and components, we can never really know where the node is detached from the DOM
@balessan about your concerns regarding the populate, I added some tests and it still works, at least for a simple usage. Can you show me an example of what you are doing in Coopstarter so I can update if needed?
On this particular title it is not an issue I think.
If all the elements of the page blink that might be a bit weird though. In other views it might prove problematic, I don't know.
@sylvain I am able to block outdated rendering when they are waiting for the response of the server requests.
Once the rendering starts, it needs to render all the children, and the children of children... It cannot be stopped. To workaround this, I delay the appendChild of the sibDisplays with setTimeouts: !346 (diffs)
It may not be very clear, don't hesitate to call if you need more informations
@alexbourlier In this app, I don't see many other places where it happens because the whole pages are rendering each time we click in the left menu, so then it's already loaded when you go to the circles informations of form.
But I would be curious to see how it reacts in other apps too
I think what should happen is that the rendering context should be invalidated. Is it possible to make the rendering in an HTML fragment? I'm wondering if the rendering could be blocked by removing the HTML Fragment.
I think we can render in fragment but as long as it's not inserted in the DOM, the callbacks of Custom Elements are not called, so the populate is not launched. (to be confirmed)
Once the fragment is inserted in the DOM, I think than removing it won't block rendering (to be confirmed to).
So in conclusion, the callbacks are called when a web component is added to a fragment.
However, we cannot add the fragment to the DOM and then add children in the fragment. So we cannot remove a fragment to stop rendering.
Is it what you were asking @sylvain ?
Yes agree. That's what I was trying to propose in the MR. So we need a way to invalidate the rendering context for:
widgets - proposal: when the widget is not in the DOM, the rendering is useless
components - proposal: keep in memory the data-src and check if it has been changed. Problem: it only works when the data-src changes -> We can find a better option.
Once we said that, we probably need to find a way to stop the rendering when it's useless
Yes but that's not exactly the same thing. I'm not talking about blocking the context, but gettting it to render outside the DOM. We need to check which solution costs less.
I can try to evaluate that indeed. But even if it's rendered outside the DOM, we should make it non-blocking so that if a new render comes up, it can start rendering asap.
Actually maybe the question here is "how to make a rendering non blocking", and the question of knowing if it needs to be cancelled or made outside of the DOM is another issue
That's what I'm not sure to understand.
I think that when we set a sib-display source, we start updateDOM function which then add elements inside the component using await and for await.
Then if you change the source multiple times quickly, the new updateDOM functions are just added at the end of the queue, which means that it needs to finish the rendering of all the previous sib-display before starting the new one. Does it make sense?
Seems fixed with the introduction of lit-html which speed up the rendering and the refactoring of the router which prevent all the components to refresh all the time