r3bl-org/r3bl-open-core

[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

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>

} else {
let mut render_ops = render_ops!();
EditorEngineApi::render_content(&render_args, &mut render_ops);
EditorEngineApi::render_selection(&render_args, &mut render_ops);
EditorEngineApi::render_caret(&render_args, &mut render_ops);
let mut render_pipeline = render_pipeline!();
render_pipeline.push(ZOrder::Normal, render_ops);
render_pipeline
}

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.