[tui][edi] Speed up editor component by caching its rendered output if content (and viewport) haven't changed
Closed this issue · 2 comments
Currently when any input event is applied to the editor component and buffer, the entire viewport content has to be regenerated. This means that even for caret movements, this will result in the entire Vec being converted into RenderOps.
Currently we also have undo/redo support, so we do know when certain input events cause this undo history to be changed, and when they do not.
It should be possible to combine the mechanics of detecting when input events require a new render to occur, as well as cache the rendered output, so that it does not have to be generated on each input event. Further, since the rendered output is clipped to a specific viewport (at the time of rendering) the viewport should be a key to determine whether the pre-existing (if any) renderops are usable (cache hit) or they're not (cache miss).
Notes
- To get scroll_offset to figure out what the viewport is, here's the code - https://github.com/r3bl-org/r3bl-open-core/blob/main/tui/src/tui/editor/editor_buffer/editor_buffer_struct.rs#L178
- Here is the code path that renders the contents of an editor - https://github.com/r3bl-org/r3bl-open-core/blob/main/tui/src/tui/editor/editor_engine/editor_engine_api.rs#L146
- Here's the main logic to determine what needs to be rendered (content, selection, caret, etc) - https://github.com/r3bl-org/r3bl-open-core/blob/main/tui/src/tui/editor/editor_engine/editor_engine_api.rs#L135
- Just the content needs to be cached in the editor buffer struct. If an entry exists for a scroll offset, then it should be used. Otherwise, that cached rendered output should be dropped, and the content should be recreated (and then cached for the next time an input event occurs).
More notes
Also instead of the scroll offset, maybe a key can be a String which is the concatenation of ScrollOffset (Position) and window size (Size)
The cache should be in EditorBuffer and of the type HashMap<String, RenderOps>
r3bl-open-core/tui/src/tui/editor/editor_engine/editor_engine_api.rs
Lines 132 to 142 in aa364e9
Only the call to render_content()
should be skipped if there is a cache hit, and not calls to render_selection()
, or render_caret()
.
So the cache hit / miss check needs to go in link 135. So if you are able to find a RenderOps
in the cache, then it should be used. Otherwise, the existing code should run (which should then store the render ops in the cache).
The "syn hi" and "MD parsing" code paths only run in the render_content()
function, which is where needless re-rendering happens (if the content OR viewport don't change).
Also you have to invalidate the cache when the editor contents have been mutated in any way. Using the ScrollOffset
as the key to the hash map takes care of scroll changes and viewport changes. Also when invalidating the cache, you can just clear the entire hash map.
Maybe the hash map needs to store more information. Like the height and width as well.
If more than just the scroll offset, such as window height & width, is used as the key, then this should take care of resize events.
Resize events will invalidate the cache, since the window size width+height won't be the same.
@nazmulidris While implementing the cache logic, I feel key is missing something. Correct me if wrong,
Scroll Offset and Window Size are neccessary but not sufficient. We have no track of changed content. (Eg: I write a letter 'H'). Perhaps scroll offset and window size both remains same and the renderops doesn't know about the existence of 'H'. So, Key should also track the editor buffer lines I feel. Is there a simpler way for this ? Or maybe I am missing something over here ?
Edit :
I think I already got my answer. If the content is changed then it has to be rendered anyway and the cache is not something which can handle this out. So with change in content we have to render it for sure. And thus, We can keep that logic separate and thus key doesn't contain the buffer lines.
@Harshil-Jani I agree. Similar to what happens w/ undo and redo, there are some input events which will modify the content and we know this "ahead of time" or "up front". This means that we can simply invalidate the cache, when we would add something to the undo history.