From c6687237e17673bfcd9a75f12730ea078faa0262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Wed, 30 Oct 2024 19:06:06 +0800 Subject: [PATCH] perf: introduce reflow for the rendering engine (#1863) --- yazi-config/src/layout.rs | 2 +- yazi-fm/src/app/commands/mod.rs | 1 + yazi-fm/src/app/commands/reflow.rs | 56 ++++++++++++++++++++++ yazi-fm/src/app/commands/resize.rs | 2 +- yazi-fm/src/components/progress.rs | 2 +- yazi-fm/src/root.rs | 10 +++- yazi-plugin/preset/components/current.lua | 8 ++-- yazi-plugin/preset/components/entity.lua | 2 +- yazi-plugin/preset/components/header.lua | 10 ++-- yazi-plugin/preset/components/linemode.lua | 2 +- yazi-plugin/preset/components/marker.lua | 2 +- yazi-plugin/preset/components/parent.lua | 10 ++-- yazi-plugin/preset/components/preview.lua | 4 +- yazi-plugin/preset/components/progress.lua | 10 ++-- yazi-plugin/preset/components/rail.lua | 10 ++-- yazi-plugin/preset/components/root.lua | 16 +++++-- yazi-plugin/preset/components/status.lua | 12 +++-- yazi-plugin/preset/components/tab.lua | 16 +++++-- yazi-plugin/preset/plugins/folder.lua | 8 ++-- yazi-plugin/src/utils/call.rs | 6 +-- 20 files changed, 140 insertions(+), 49 deletions(-) create mode 100644 yazi-fm/src/app/commands/reflow.rs diff --git a/yazi-config/src/layout.rs b/yazi-config/src/layout.rs index 80b3d986f..1f42a562d 100644 --- a/yazi-config/src/layout.rs +++ b/yazi-config/src/layout.rs @@ -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, diff --git a/yazi-fm/src/app/commands/mod.rs b/yazi-fm/src/app/commands/mod.rs index 8c920955a..2e1fc09a0 100644 --- a/yazi-fm/src/app/commands/mod.rs +++ b/yazi-fm/src/app/commands/mod.rs @@ -4,6 +4,7 @@ yazi_macro::mod_flat!( notify plugin quit + reflow render resize resume diff --git a/yazi-fm/src/app/commands/reflow.rs b/yazi-fm/src/app/commands/reflow.rs new file mode 100644 index 000000000..bb18acef8 --- /dev/null +++ b/yazi-fm/src/app/commands/reflow.rs @@ -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 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::() { + 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}"); + } + } +} diff --git a/yazi-fm/src/app/commands/resize.rs b/yazi-fm/src/app/commands/resize.rs index a8fbe0ad4..250226c74 100644 --- a/yazi-fm/src/app/commands/resize.rs +++ b/yazi-fm/src/app/commands/resize.rs @@ -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); diff --git a/yazi-fm/src/components/progress.rs b/yazi-fm/src/components/progress.rs index a9423ff3b..4091cc3ff 100644 --- a/yazi-fm/src/components/progress.rs +++ b/yazi-fm/src/components/progress.rs @@ -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>("partial_render", ())? { + for widget in comp.call_method::<_, Vec>("partial_redraw", ())? { let Some(w) = cast_to_renderable(&widget) else { continue }; let area = w.area(); diff --git a/yazi-fm/src/root.rs b/yazi-fm/src/root.rs index 60d607462..d667cc3ab 100644 --- a/yazi-fm/src/root.rs +++ b/yazi-fm/src/root.rs @@ -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> { + 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); diff --git a/yazi-plugin/preset/components/current.lua b/yazi-plugin/preset/components/current.lua index cca8d0428..2a8bdebbf 100644 --- a/yazi-plugin/preset/components/current.lua +++ b/yazi-plugin/preset/components/current.lua @@ -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 { diff --git a/yazi-plugin/preset/components/entity.lua b/yazi-plugin/preset/components/entity.lua index 5da34f022..852fbb780 100644 --- a/yazi-plugin/preset/components/entity.lua +++ b/yazi-plugin/preset/components/entity.lua @@ -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) diff --git a/yazi-plugin/preset/components/header.lua b/yazi-plugin/preset/components/header.lua index 2cf3289ff..1a59a09d5 100644 --- a/yazi-plugin/preset/components/header.lua +++ b/yazi-plugin/preset/components/header.lua @@ -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) diff --git a/yazi-plugin/preset/components/linemode.lua b/yazi-plugin/preset/components/linemode.lua index 42c260084..6d59c4230 100644 --- a/yazi-plugin/preset/components/linemode.lua +++ b/yazi-plugin/preset/components/linemode.lua @@ -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) diff --git a/yazi-plugin/preset/components/marker.lua b/yazi-plugin/preset/components/marker.lua index 9a3c9d382..dea96bab1 100644 --- a/yazi-plugin/preset/components/marker.lua +++ b/yazi-plugin/preset/components/marker.lua @@ -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 diff --git a/yazi-plugin/preset/components/parent.lua b/yazi-plugin/preset/components/parent.lua index c9ecdd60a..b257f82c7 100644 --- a/yazi-plugin/preset/components/parent.lua +++ b/yazi-plugin/preset/components/parent.lua @@ -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 diff --git a/yazi-plugin/preset/components/preview.lua b/yazi-plugin/preset/components/preview.lua index 63b5c6d6a..87eaf0312 100644 --- a/yazi-plugin/preset/components/preview.lua +++ b/yazi-plugin/preset/components/preview.lua @@ -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) diff --git a/yazi-plugin/preset/components/progress.lua b/yazi-plugin/preset/components/progress.lua index 0bddd11a7..480644446 100644 --- a/yazi-plugin/preset/components/progress.lua +++ b/yazi-plugin/preset/components/progress.lua @@ -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 {} } diff --git a/yazi-plugin/preset/components/rail.lua b/yazi-plugin/preset/components/rail.lua index 52f37b7d5..8f6011ef6 100644 --- a/yazi-plugin/preset/components/rail.lua +++ b/yazi-plugin/preset/components/rail.lua @@ -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 diff --git a/yazi-plugin/preset/components/root.lua b/yazi-plugin/preset/components/root.lua index 61fc6cd0e..d373bf425 100644 --- a/yazi-plugin/preset/components/root.lua +++ b/yazi-plugin/preset/components/root.lua @@ -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 diff --git a/yazi-plugin/preset/components/status.lua b/yazi-plugin/preset/components/status.lua index 5fe7b5e04..e1b4cfa6c 100644 --- a/yazi-plugin/preset/components/status.lua +++ b/yazi-plugin/preset/components/status.lua @@ -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) diff --git a/yazi-plugin/preset/components/tab.lua b/yazi-plugin/preset/components/tab.lua index 7d7abdc52..1246f1106 100644 --- a/yazi-plugin/preset/components/tab.lua +++ b/yazi-plugin/preset/components/tab.lua @@ -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 diff --git a/yazi-plugin/preset/plugins/folder.lua b/yazi-plugin/preset/plugins/folder.lua index 7ccb0f0b4..ab8f96ea3 100644 --- a/yazi-plugin/preset/plugins/folder.lua +++ b/yazi-plugin/preset/plugins/folder.lua @@ -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 diff --git a/yazi-plugin/src/utils/call.rs b/yazi-plugin/src/utils/call.rs index 968e328ae..1854e92e5 100644 --- a/yazi-plugin/src/utils/call.rs +++ b/yazi-plugin/src/utils/call.rs @@ -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,