[Optimization] Reduce unnecessary Component
rebuilds
#1754
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The current implementation of
Component
results in many unnecessary rebuilds when components are nested inside the view of other components. This can be observed in the following example, with several layers of nested components (code):unoptimized.mov
This PR optimizes
Component
to avoid unnecessary rebuilds, after which running the same example results in:optimized.mov
The cause of excessive rebuilding in the existing implementation is that the component's underlying element is rebuilt eagerly after any potential state mutation. If the component returns a message from
update
, processing that message at a parent component (or the root application level), will in turn cause another rebuild of the component just built. As a result, with increasing layers of components, the runtime of processing an event which propagates to the root of the application increases in a non-linear fashion.By deferring rebuild until any widget method is invoked where the latest element is actually required, we can perform this same process in linear time.
If the rebuild is to be deferred until absolutely necessary, invocation of any widget method may require rebuilding of the component's element, and in turn requires diffing of that element's widget sub-tree. This means that mutable access to the element's widget sub-tree is required during every widget method. To achieve this, the widget tree for the component's contained element is moved inside of the component-widget's state and put inside a
RefCell
. Sincewidth
,height
andlayout
do not have even immutable access to the widget tree, but may still be invoked and require rebuilding of the element, anRc
is used to store a reference to the sub-tree on the component-widget instance itself, and populated during diffing (similar to the approach used in theLazy
widget). This allows mutable access to the element's widget tree from any widget method without altering theWidget
interface.For
overlay
, the contained element's widget sub-tree is moved onto the overlay element, and restored during the overlay's drop (again, similar to how overlay works in theLazy
widget).This implementation has been tested in a relatively large real-world application having many layers of components both inside & outside overlays. No issues or discrepancies from the current implementation of
Component
have yet been identified.