perf: introduce reflow for the rendering engine (#1863)

This commit is contained in:
三咲雅 · Misaki Masa 2024-10-30 19:06:06 +08:00 committed by GitHub
parent 81b8c89ec1
commit c6687237e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 140 additions and 49 deletions

View File

@ -1,6 +1,6 @@
use ratatui::layout::Rect;
#[derive(Clone, Copy, Default)]
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct Layout {
pub current: Rect,
pub preview: Rect,

View File

@ -4,6 +4,7 @@ yazi_macro::mod_flat!(
notify
plugin
quit
reflow
render
resize
resume

View File

@ -0,0 +1,56 @@
use std::sync::Arc;
use mlua::Value;
use ratatui::layout::Position;
use tracing::error;
use yazi_config::LAYOUT;
use yazi_macro::render;
use yazi_shared::event::Cmd;
use crate::{Root, app::App, lives::Lives};
struct Opt;
impl From<Cmd> for Opt {
fn from(_: Cmd) -> Self { Self }
}
impl From<()> for Opt {
fn from(_: ()) -> Self { Self }
}
impl App {
#[yazi_codegen::command]
pub fn reflow(&mut self, _: Opt) {
let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { return };
let mut layout = *LAYOUT.load_full();
let result = Lives::scope(&self.cx, |_| {
let comps = Root::reflow((Position::ORIGIN, size).into())?;
for v in comps.sequence_values::<Value>() {
let Value::Table(t) = v? else {
error!("`reflow()` must return a table of components");
continue;
};
let id: mlua::String = t.get("_id")?;
match id.to_str()? {
"current" => layout.current = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?,
"preview" => layout.preview = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?,
_ => {}
}
}
Ok(())
});
if layout != *LAYOUT.load_full() {
LAYOUT.store(Arc::new(layout));
render!();
}
if let Err(e) = result {
error!("Failed to `reflow()` the `Root` component:\n{e}");
}
}
}

View File

@ -16,7 +16,7 @@ impl App {
#[yazi_codegen::command]
pub fn resize(&mut self, _: Opt) {
self.cx.manager.active_mut().preview.reset();
self.render();
self.reflow(());
self.cx.manager.current_mut().sync_page(true);
self.cx.manager.hover(None);

View File

@ -13,7 +13,7 @@ impl Progress {
let mut patches = vec![];
let mut f = || {
let comp: Table = LUA.globals().raw_get("Progress")?;
for widget in comp.call_method::<_, Vec<AnyUserData>>("partial_render", ())? {
for widget in comp.call_method::<_, Vec<AnyUserData>>("partial_redraw", ())? {
let Some(w) = cast_to_renderable(&widget) else { continue };
let area = w.area();

View File

@ -12,6 +12,12 @@ pub(super) struct Root<'a> {
impl<'a> Root<'a> {
pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } }
pub(super) fn reflow<'lua>(area: Rect) -> mlua::Result<Table<'lua>> {
let area = yazi_plugin::elements::Rect::from(area);
let root = LUA.globals().raw_get::<_, Table>("Root")?.call_method::<_, Table>("new", area)?;
root.call_method("reflow", ())
}
}
impl<'a> Widget for Root<'a> {
@ -20,11 +26,11 @@ impl<'a> Widget for Root<'a> {
let area = yazi_plugin::elements::Rect::from(area);
let root = LUA.globals().raw_get::<_, Table>("Root")?.call_method::<_, Table>("new", area)?;
render_widgets(root.call_method("render", ())?, buf);
render_widgets(root.call_method("redraw", ())?, buf);
Ok::<_, mlua::Error>(())
};
if let Err(e) = f() {
error!("Failed to render the `Root` component:\n{e}");
error!("Failed to redraw the `Root` component:\n{e}");
}
components::Preview::new(self.cx).render(area, buf);

View File

@ -23,7 +23,9 @@ function Current:empty()
}
end
function Current:render()
function Current:reflow() return { self } end
function Current:redraw()
local files = self._folder.window
if #files == 0 then
return self:empty()
@ -31,8 +33,8 @@ function Current:render()
local entities, linemodes = {}, {}
for _, f in ipairs(files) do
entities[#entities + 1] = Entity:new(f):render()
linemodes[#linemodes + 1] = Linemode:new(f):render()
entities[#entities + 1] = Entity:new(f):redraw()
linemodes[#linemodes + 1] = Linemode:new(f):redraw()
end
return {

View File

@ -76,7 +76,7 @@ function Entity:symlink()
return to and ui.Line(" -> " .. tostring(to)):italic() or ui.Line {}
end
function Entity:render()
function Entity:redraw()
local lines = {}
for _, c in ipairs(self._children) do
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)

View File

@ -95,11 +95,13 @@ function Header:tabs()
return ui.Line(spans)
end
function Header:render()
local right = self:children_render(self.RIGHT)
function Header:reflow() return { self } end
function Header:redraw()
local right = self:children_redraw(self.RIGHT)
self._right_width = right:width()
local left = self:children_render(self.LEFT)
local left = self:children_redraw(self.LEFT)
return {
ui.Text(left):area(self._area),
@ -135,7 +137,7 @@ function Header:children_remove(id, side)
end
end
function Header:children_render(side)
function Header:children_redraw(side)
local lines = {}
for _, c in ipairs(side == self.RIGHT and self._right or self._left) do
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)

View File

@ -62,7 +62,7 @@ function Linemode:owner()
return ui.Line(string.format("%s:%s", user or "-", group or "-"))
end
function Linemode:render()
function Linemode:redraw()
local lines = {}
for _, c in ipairs(self._children) do
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)

View File

@ -9,7 +9,7 @@ function Marker:new(area, folder)
}, { __index = self })
end
function Marker:render()
function Marker:redraw()
if self._area.w * self._area.h == 0 then
return {}
elseif not self._folder or #self._folder.window == 0 then

View File

@ -10,18 +10,20 @@ function Parent:new(area, tab)
}, { __index = self })
end
function Parent:render()
function Parent:reflow() return { self } end
function Parent:redraw()
if not self._folder then
return {}
end
local items = {}
local entities = {}
for _, f in ipairs(self._folder.window) do
items[#items + 1] = Entity:new(f):render()
entities[#entities + 1] = Entity:new(f):redraw()
end
return {
ui.List(items):area(self._area),
ui.List(entities):area(self._area),
}
end

View File

@ -10,7 +10,9 @@ function Preview:new(area, tab)
}, { __index = self })
end
function Preview:render() return {} end
function Preview:reflow() return { self } end
function Preview:redraw() return {} end
-- Mouse events
function Preview:click(event, up)

View File

@ -2,20 +2,20 @@ Progress = {
_area = ui.Rect.default, -- TODO: remove this
}
function Progress:render(area, offset)
function Progress:redraw(area, offset)
self._area = ui.Rect {
x = math.max(0, area.w - offset - 21),
y = area.y,
w = ya.clamp(0, area.w - offset - 1, 20),
h = math.min(1, area.h),
}
return self:partial_render()
return self:partial_redraw()
end
-- Progress bars usually need frequent updates to report the latest task progress.
-- We use `partial_render()` to partially render it when there is progress change,
-- which has almost no cost compared to a full render by `render()`.
function Progress:partial_render()
-- We use `partial_redraw()` to partially redraw it when there is progress change,
-- which has almost no cost compared to a full redraw by `redraw()`.
function Progress:partial_redraw()
local progress = cx.tasks.progress
if progress.total == 0 then
return { ui.Text {} }

View File

@ -20,12 +20,14 @@ function Rail:build()
}
end
function Rail:render()
local children = self._base or {}
function Rail:reflow() return { self } end
function Rail:redraw()
local elements = self._base or {}
for _, child in ipairs(self._children) do
children = ya.list_merge(children, ya.render_with(child))
elements = ya.list_merge(elements, ya.redraw_with(child))
end
return children
return elements
end
-- Mouse events

View File

@ -29,12 +29,20 @@ function Root:build()
}
end
function Root:render()
local children = self._base or {}
function Root:reflow()
local components = { self }
for _, child in ipairs(self._children) do
children = ya.list_merge(children, ya.render_with(child))
components = ya.list_merge(components, child:reflow())
end
return children
return components
end
function Root:redraw()
local elements = self._base or {}
for _, child in ipairs(self._children) do
elements = ya.list_merge(elements, ya.redraw_with(child))
end
return elements
end
-- Mouse events

View File

@ -131,16 +131,18 @@ function Status:position()
}
end
function Status:render()
local left = self:children_render(self.LEFT)
function Status:reflow() return { self } end
local right = self:children_render(self.RIGHT)
function Status:redraw()
local left = self:children_redraw(self.LEFT)
local right = self:children_redraw(self.RIGHT)
local right_width = right:width()
return {
ui.Text(left):area(self._area),
ui.Text(right):area(self._area):align(ui.Text.RIGHT),
table.unpack(Progress:render(self._area, right_width)),
table.unpack(Progress:redraw(self._area, right_width)),
}
end
@ -172,7 +174,7 @@ function Status:children_remove(id, side)
end
end
function Status:children_render(side)
function Status:children_redraw(side)
local lines = {}
for _, c in ipairs(side == self.RIGHT and self._right or self._left) do
lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self)

View File

@ -29,12 +29,20 @@ function Tab:build()
}
end
function Tab:render()
local children = self._base or {}
function Tab:reflow()
local components = { self }
for _, child in ipairs(self._children) do
children = ya.list_merge(children, ya.render_with(child))
components = ya.list_merge(components, child:reflow())
end
return children
return components
end
function Tab:redraw()
local elements = self._base or {}
for _, child in ipairs(self._children) do
elements = ya.list_merge(elements, ya.redraw_with(child))
end
return elements
end
-- Mouse events

View File

@ -17,14 +17,14 @@ function M:peek()
})
end
local items = {}
local entities = {}
for _, f in ipairs(folder.window) do
items[#items + 1] = Entity:new(f):render()
entities[#entities + 1] = Entity:new(f):redraw()
end
ya.preview_widgets(self, {
ui.List(items):area(self.area),
table.unpack(Marker:new(self.area, folder):render()),
ui.List(entities):area(self.area),
table.unpack(Marker:new(self.area, folder):redraw()),
})
end

View File

@ -37,7 +37,7 @@ impl Utils {
)?;
ya.raw_set(
"render_with",
"redraw_with",
lua.create_function(|lua, c: Table| {
let id: mlua::String = c.get("_id")?;
let id = id.to_str()?;
@ -58,9 +58,9 @@ impl Utils {
_ => {}
}
match c.call_method::<_, Table>("render", ()) {
match c.call_method::<_, Table>("redraw", ()) {
Err(e) => {
error!("Failed to `render()` the `{id}` component:\n{e}");
error!("Failed to `redraw()` the `{id}` component:\n{e}");
lua.create_table()
}
ok => ok,