Skip to content

Commit

Permalink
perf: introduce reflow for the rendering engine (#1863)
Browse files Browse the repository at this point in the history
  • Loading branch information
sxyazi authored Oct 30, 2024
1 parent 81b8c89 commit c668723
Show file tree
Hide file tree
Showing 20 changed files with 140 additions and 49 deletions.
2 changes: 1 addition & 1 deletion yazi-config/src/layout.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
1 change: 1 addition & 0 deletions yazi-fm/src/app/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ yazi_macro::mod_flat!(
notify
plugin
quit
reflow
render
resize
resume
Expand Down
56 changes: 56 additions & 0 deletions yazi-fm/src/app/commands/reflow.rs
Original file line number Diff line number Diff line change
@@ -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}");
}
}
}
2 changes: 1 addition & 1 deletion yazi-fm/src/app/commands/resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion yazi-fm/src/components/progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
10 changes: 8 additions & 2 deletions yazi-fm/src/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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);
Expand Down
8 changes: 5 additions & 3 deletions yazi-plugin/preset/components/current.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,18 @@ 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()
end

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 {
Expand Down
2 changes: 1 addition & 1 deletion yazi-plugin/preset/components/entity.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 6 additions & 4 deletions yazi-plugin/preset/components/header.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion yazi-plugin/preset/components/linemode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion yazi-plugin/preset/components/marker.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions yazi-plugin/preset/components/parent.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion yazi-plugin/preset/components/preview.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions yazi-plugin/preset/components/progress.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 {} }
Expand Down
10 changes: 6 additions & 4 deletions yazi-plugin/preset/components/rail.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 12 additions & 4 deletions yazi-plugin/preset/components/root.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions yazi-plugin/preset/components/status.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
16 changes: 12 additions & 4 deletions yazi-plugin/preset/components/tab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions yazi-plugin/preset/plugins/folder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions yazi-plugin/src/utils/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;
Expand All @@ -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,
Expand Down

0 comments on commit c668723

Please sign in to comment.