Conversation
|
@sbatten hi! This looks like nice work and I will gladly review this and test it with you. Do you have a scenario where the previous solution was showing this slow perf? How can we easily measure that this new approach is an improvement? |
|
|
||
| addChild(node: Node, size: number | Sizing, index: number): void { | ||
| addChild(node: Node, size: number | Sizing, index: number, skipLayout?: boolean): void { | ||
| if (index < 0 || index > this.children.length) { |
There was a problem hiding this comment.
skipLayout is not being used. Why are we adding it?
| @@ -31,51 +31,131 @@ | |||
| <script> | |||
There was a problem hiding this comment.
Great that you added tests! I see they pass, I will not review them.
There was a problem hiding this comment.
@isidorn Everything in test/splitview are just visual manual tests, like a playground, you can safely ignore that. Proper unit tests are in place to make sure all this works.
| const snappedBefore = typeof snapBeforeIndex === 'number' && !this.viewItems[snapBeforeIndex].visible; | ||
| const snappedAfter = typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible; | ||
|
|
||
| if (snappedBefore && collapsesUp[index]) { |
There was a problem hiding this comment.
I do not understand the collapsesUp and collapsesDown change.
This does not seem related to the serialization in the grid or am I missing something?
There was a problem hiding this comment.
this change fixes a bug that I thought was only in my branch but it actually reproduces in insiders as well, I can pull this out, but you can repro the issue by hiding the sidebar, toggling its position, then trying to drag it out (unsnap it). It will not unsnap. If needed, I can move this to a separate bug fix.
There was a problem hiding this comment.
Yeah I'd just put this one in master directly.
| setVisible?(visible: boolean): void; | ||
| } | ||
|
|
||
| export interface ISerializableView extends IView { |
There was a problem hiding this comment.
This is introducing the concept of serialization to the SplitView.
I think the SplitView should have no notion of this concept, but instead the clients of the SplitView
| this.style(options.styles || defaultStyles); | ||
|
|
||
| // We have an existing set of view, add them now | ||
| if (options.descriptor) { |
There was a problem hiding this comment.
Why is the splitView doing this? Why doesn't the client simply construct the new SplitView and add all the deserialized views and says skip the layout.
If we went with this approach I think we can dump the ISplitViewDescriptor
There was a problem hiding this comment.
SplitView was kind of an easy case for adding skipLayout, but I don't actually like having that as part of its public api, especially since it doesn't match well with GridView's branch node api. To make this look more correct optically and avoid people calling with skiplayout, I've made that a private call.
There was a problem hiding this comment.
We don't want to add view by view and skipping the layout until the end. This will still cause all the math to happen in the background. Plus it will expose a terrible skipLayout API. As per comment above, we want a serializable Splitview which can deserialize in a single layout call.
| return this._getViews(node, this.orientation, { top: 0, left: 0, width: this.width, height: this.height }); | ||
| } | ||
|
|
||
| static deserialize<T extends ISerializableView>(json: ISerializedGridView, deserializer: IViewDeserializer<T>, options: IGridViewOptions = {}): GridView { |
There was a problem hiding this comment.
I feel like this does not belong here.
So this is a GridView which so far had no notion of serialization and we are adding it.
I thought that is the point of the SerializedGird.
Same as my previous comment, serialization should not be done on this level but on one level higher it feels.
There was a problem hiding this comment.
With this PR both the Splitview and Gridview will support serialization. The idea is to also get rid of the SerializableGrid, since the Grid will be able to deserialize.
| super(); | ||
| this.gridview = new GridView(options); | ||
|
|
||
| if (view instanceof GridView) { |
There was a problem hiding this comment.
Can you please clarify this new case when the view is instanceof GridView. Since this seems to not be covered before
There was a problem hiding this comment.
Grid historically created a grid view and then added views, we know this has pitfalls due to layouts for each added view. This allows us to provide a preconstructed one.
There was a problem hiding this comment.
Ya this is weird, I'd rather have a public deserialize method for this. But that's just API preference.
|
@sbatten I did an initial review and added comments - which are more like questions. So it would be great if you could adress them or explain to me more about the reasoning behind why we choose to do it in this way - if it is easier we can also talk via Teams (I should be online for another 90 minutes). Can you also explain more about why the new serialisation tehnique is better than the previous one - will make it easier for me to digest the code since I have no prior deep understanding. |
|
@isidorn I've updated the PR description with more context and added some responses to your comments as well. Lmk if more information is desired. |
@isidorn Deserializing a splitview/grid today requires additively modifying the widget from an empty state into the fully reconstructed state, adding view by view. This is expensive because in runs through all the layout code in |
joaomoreno
left a comment
There was a problem hiding this comment.
@sbatten I'm not a big fan of deserialize2. Since this is a PR, just go ahead and replace the current deserialize code with the new approach. This will help you validate the implementation in two ways: by benefiting from the already existing test suite and by seeing it work in the editor grid. You're going to have to do that work eventually, better do it today than in debt week.
| const snappedBefore = typeof snapBeforeIndex === 'number' && !this.viewItems[snapBeforeIndex].visible; | ||
| const snappedAfter = typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible; | ||
|
|
||
| if (snappedBefore && collapsesUp[index]) { |
There was a problem hiding this comment.
Yeah I'd just put this one in master directly.
| this.state = State.Idle; | ||
|
|
||
| if (typeof size !== 'number' && size.type === 'distribute') { | ||
| this.distributeViewSizes(); |
There was a problem hiding this comment.
distributeViewSizes is expensive, we very likely also want to skip this if skipLayout.
| views: { | ||
| visible?: boolean; | ||
| size: number; | ||
| view: IView; |
There was a problem hiding this comment.
@sbatten I might be wrong but I believe this won't work in the big picture. Remember that a gridview is a tree of splitviews. In order to fully deserialize a whole gridview in one step, you're very likely going to have to use the deserializer pattern in which a fromJSON method is called, even down in the splitview.
There was a problem hiding this comment.
the grid view deserializes views using a depth first search so this shouldn't be an issue
|
@joaomoreno thanks for the review |
|
@isidorn I've addressed @joaomoreno's feedback and now have a single deserialize call. With this change and some modification, the new deserialize API passes all unit tests and sanity manual tests. I am more confident in it now and think it should be ready for testing. @joaomoreno Interesting point about the latest commit. One of the unit tests was failing because it was testing that the grid would resize all views proportionally on the first layout call. This logic is actually counter to how the workbench should work. We don't usually want to scale out the sidebar or panel when we get some extra space. And today if the window is smaller than the panel, it will just take most of the screen in legacy layout. With that observation, I have reserved this proportion-preserving behavior only for grids with proportonalLayout enabled. That means I don't need to do anything special in the initial layout, because it is functionally no different from any other layout. I simply needed to initialize the proportions in SplitView to make this work. |
|
@sbatten thanks for adressing the feedback. I plan to test the branch out today. Will update this comment with my findings. |
|
@sbatten I have tested this and it works nicely. I am especially happy about the Panel maximize now being able to take the full view. As for the performance, when running out of source from this branch with your new serialization improvements (after 4 runs on average) I get Since the layout seems to works and we should start self hosting on this I am approving this PR. Thanks for the nice work |
|
fyi @jrieken for potential minor startup perf hit. Based on my local measuring workbenchLoad is 2% slower with the new grid layout. |
|
But this should still be a perf gain when comparing to today's insiders |
* add deserialization to splitview * add new grid deserialization test to index.html * working mostly * branchnode should invert orthogonality in splitview * fix issue with sidebar snapping * cleanup and make splitview api more explicit * hook up children sash reset * adopt single deserialize (PR feedback)
Summary
deserialize2to avoid mucking with anything the editor grid is doing)index.htmlpage to allow playing with it.layout.tsI've played around with this and it feels good and shows a great perf improvement, but as we all know testing ones own code can suffer from tunnel vision.
I know @joaomoreno is out so I'm looking for some help getting this tested and safely in.
Perf Improvments
The primary goal of this work is to improve workbench startup time. As measured on my machine, the current grid layout performs ~40ms slower than the legacy layout on initial startup time. I am using the startup performance command to look at markers. I have the below patch for more fine grained measurement. On my machine, these changes recover >75% of that lost time.
Why was it slow?
The way we deserialize today is by adding views individually at the grid level (not grid view, or splitview level). What that means is that every addition of a view triggers a layout call for that view and additionally for the rest of the views. Joao did some work to make this faster by trying to delay layout calls until the end of the deserialization, but the issue remained because not enough information was initialized during the deserialization process so the initial layout call triggered many recalculations. Instead of dealing with layout controllers, the proper fix as Joao and I discussed was to bring this logic down to the individual view types. Now the initial layout calls layout on each part exactly once during startup.