Optimize change directory performance

This PR breaks the custom layout renderer API by deprecating the
following heavyweight fields in the Lua Context passed to the renderer
functions.

The following fields are being deprecated:

- app.directory_buffer
- app.history
- app.last_modes

However, there's no change in the Lua Context passed to the functions
called via `CallLua*` messages.

Closes: https://github.com/sayanarijit/xplr/issues/418
pull/424/head
Arijit Basu 2 years ago committed by Arijit Basu
parent e370c25bc5
commit 8f5e5491f2

@ -410,7 +410,7 @@ It contains the following information:
- [screen_size][37]
- [app][38]
## Size
### Size
It contains the following information:
@ -421,6 +421,25 @@ It contains the following information:
Every field is of integer type.
### app
This is a lightweight version of the [Lua Context][39]. In this context, the
heavyweight fields like [directory_buffer][50] are omitted for performance
reasons.
Hence, only the following fields are avilable.
- [version][40]
- [pwd][41]
- [focused_node][42]
- [selection][43]
- [mode][44]
- [layout][45]
- [input_buffer][46]
- [pid][47]
- [session_path][48]
- [explorer_config][49]
[1]: #builtin
[2]: #custom
[3]: #layout
@ -458,4 +477,16 @@ Every field is of integer type.
[35]: #content-renderer
[36]: #content-renderer-argument
[37]: #size
[38]: message.md#calllua-argument
[38]: #app
[39]: message.md#lua-context
[40]: message.md#version
[41]: message.md#pwd
[42]: message.md#focused_node
[43]: message.md#selection
[44]: message.md#mode
[45]: message.md#layout
[46]: message.md#input_buffer
[47]: message.md#pid
[48]: message.md#session_path
[49]: message.md#explorer_config
[50]: message.md#directory_buffer

@ -445,8 +445,8 @@ stderr will be piped to null. So it's non-interactive.
**YAML:** `CallLua: string`
Call a Lua function.
A [`CallLuaArg`][14] object will be passed to the
[function][3] as argument.
A [Lua Context][14] object will be passed to the [function][3] as argument.
The function can optionally return a list of messages for xplr to handle
after the executing the function.
@ -469,8 +469,8 @@ stderr will be piped to null. So it's non-interactive.
**YAML:** `LuaEval: string`
Execute Lua code without needing to define a function.
However, `CallLuaArg` won't be available.
Execute Lua code without needing to define a function. However,
[Lua Context][14] won't be available.
**YAML Example:** `LuaEval: "return { { LogInfo = io.read() } }"`
@ -856,7 +856,7 @@ When called the function receives a [special argument][14] that
contains some useful information. The function can optionally return a list of
messages which will be handled by xplr.
### CallLua Argument
### Lua Context
This is a special argument passed to the lua functions when called using the
`CallLua`, `CallLuaSilently` messages.
@ -864,7 +864,6 @@ This is a special argument passed to the lua functions when called using the
It contains the following information:
- [version][29]
- [config][30]
- [pwd][31]
- [focused_node][32]
- [directory_buffer][33]
@ -884,12 +883,6 @@ Type: string
xplr version. Can be used to test compatibility.
### config
Type: [Config][43]
The loaded configuration.
### pwd
Type: string
@ -904,7 +897,7 @@ The node under focus.
### directory_buffer
Type: nullable [DirectoryBuffer][62]
Type: nullable [Directory Buffer][62]
The directory buffer being rendered.
@ -1073,7 +1066,7 @@ A node contains the following fields:
- [canonical][58]
- [symlink][59]
### DirectoryBuffer
### Directory Buffer
Directory buffer contains the following fields:
@ -1285,11 +1278,10 @@ xplr.config.modes.builtin.default.key_bindings.on_key.space = {
[11]: layouts.md#layout
[12]: layouts.md#builtin
[13]: layouts.md#custom
[14]: #calllua-argument
[14]: #lua-context
[15]: filtering.md#filter
[16]: filtering.md
[17]: sorting.md#sorter
[18]: https://docs.rs/xplr/latest/xplr/app/struct.CallLuaArg.html#fields
[19]: configure-key-bindings.md#tutorial-adding-a-new-mode
[20]: #xplr_pipe_msg_in
[21]: #xplr_pipe_selection_out
@ -1333,7 +1325,7 @@ xplr.config.modes.builtin.default.key_bindings.on_key.space = {
[59]: #symlink
[60]: column-renderer.md#permission
[61]: column-renderer.md#resolved-node-metadata
[62]: #directorybuffer
[62]: #directory-buffer
[63]: #nodes
[64]: #total
[65]: #focus

@ -1,25 +0,0 @@
-- Nothing to see here. Please move on.
-- Or if you insist, see https://github.com/sayanarijit/xplr/issues/412
local xplr = xplr
xplr.__CACHE__ = { directory_nodes = {} }
function xplr.__CACHE__.set_directory_nodes(nodes)
xplr.__CACHE__.directory_nodes = nodes
end
function xplr.__CACHE__.call(fun, arg)
if arg.app and arg.app.directory_buffer then
arg.app.directory_buffer.nodes = xplr.__CACHE__.directory_nodes
elseif arg.directory_buffer then
arg.directory_buffer.nodes = xplr.__CACHE__.directory_nodes
end
return fun(arg)
end
function xplr.__CACHE__.caller(fun)
return function(arg)
return xplr.__CACHE__.call(fun, arg)
end
end

@ -1133,7 +1133,6 @@ pub struct Command {
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum MsgOut {
CacheDirectoryNodes(Vec<Node>),
ExplorePwdAsync,
ExploreParentsAsync,
Refresh,
@ -1247,29 +1246,12 @@ impl History {
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct CachedDirectoryBuffer {
pub parent: String,
pub total: usize,
pub focus: usize,
}
impl CachedDirectoryBuffer {
pub fn new(buf: &DirectoryBuffer) -> Self {
Self {
parent: buf.parent.clone(),
total: buf.total,
focus: buf.focus,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CallLuaArg {
pub struct LuaContextHeavy {
pub version: String,
pub pwd: String,
pub focused_node: Option<Node>,
pub directory_buffer: Option<CachedDirectoryBuffer>,
pub directory_buffer: Option<DirectoryBuffer>,
pub selection: IndexSet<Node>,
pub mode: Mode,
pub layout: Layout,
@ -1281,6 +1263,20 @@ pub struct CallLuaArg {
pub last_modes: Vec<Mode>,
}
#[derive(Debug, Clone, Serialize)]
pub struct LuaContextLight {
pub version: String,
pub pwd: String,
pub focused_node: Option<Node>,
pub selection: IndexSet<Node>,
pub mode: Mode,
pub layout: Layout,
pub input_buffer: Option<String>,
pub pid: u32,
pub session_path: String,
pub explorer_config: ExplorerConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct App {
pub version: String,
@ -2280,8 +2276,6 @@ impl App {
dir.focused_node().map(|n| n.relative_path.clone()),
)?;
if dir.parent == self.pwd {
self.msg_out
.push_back(MsgOut::CacheDirectoryNodes(dir.nodes.clone()));
self.directory_buffer = Some(dir);
}
Ok(self)
@ -2772,15 +2766,12 @@ impl App {
Ok(())
}
pub fn to_lua_arg(&self) -> CallLuaArg {
CallLuaArg {
pub fn to_lua_ctx_heavy(&self) -> LuaContextHeavy {
LuaContextHeavy {
version: self.version.clone(),
pwd: self.pwd.clone(),
focused_node: self.focused_node().cloned(),
directory_buffer: self
.directory_buffer
.as_ref()
.map(|buf| CachedDirectoryBuffer::new(buf)),
directory_buffer: self.directory_buffer.clone(),
selection: self.selection.clone(),
mode: self.mode.clone(),
layout: self.layout.clone(),
@ -2792,4 +2783,19 @@ impl App {
last_modes: self.last_modes.clone(),
}
}
pub fn to_lua_ctx_light(&self) -> LuaContextLight {
LuaContextLight {
version: self.version.clone(),
pwd: self.pwd.clone(),
focused_node: self.focused_node().cloned(),
selection: self.selection.clone(),
mode: self.mode.clone(),
layout: self.layout.clone(),
input_buffer: self.input.as_ref().map(|i| i.value().into()),
pid: self.pid,
session_path: self.session_path.clone(),
explorer_config: self.explorer_config.clone(),
}
}
}

@ -1,4 +1,3 @@
use crate::app::Node;
use crate::app::VERSION;
use crate::config::Config;
use anyhow::bail;
@ -12,7 +11,6 @@ use serde::Serialize;
use std::fs;
const DEFAULT_LUA_SCRIPT: &str = include_str!("init.lua");
const CACHE_LUA_SCRIPT: &str = include_str!("__cache__.lua");
const UPGRADE_GUIDE_LINK: &str = "https://xplr.dev/en/upgrade-guide.html";
pub fn serialize<'lua, T: Serialize + Sized>(
@ -83,7 +81,6 @@ pub fn init(lua: &Lua) -> Result<Config> {
globals.set("xplr", lua_xplr)?;
lua.load(DEFAULT_LUA_SCRIPT).set_name("init")?.exec()?;
lua.load(CACHE_LUA_SCRIPT).set_name("internal")?.exec()?;
let lua_xplr: mlua::Table = globals.get("xplr")?;
let config: Config = lua.from_value(lua_xplr.get("config")?)?;
@ -147,29 +144,6 @@ pub fn call<'lua, R: Deserialize<'lua>>(
Ok(res)
}
/// Used to call lua functions with cache support.
pub fn call_with_cache<'lua, R: Deserialize<'lua>>(
lua: &'lua Lua,
func: &str,
arg: mlua::Value<'lua>,
) -> Result<R> {
let caller: mlua::Function =
resolve_fn(&lua.globals(), "xplr.__CACHE__.call")?;
let func = format!("xplr.fn.{}", func);
let func: mlua::Function = resolve_fn(&lua.globals(), &func)?;
let res: mlua::Value = caller.call((func, arg))?;
let res: R = lua.from_value(res)?;
Ok(res)
}
/// Used to cache the directory nodes.
pub fn cache_directory_nodes(lua: &Lua, nodes: &[Node]) -> Result<()> {
let func = "xplr.__CACHE__.set_directory_nodes";
let func: mlua::Function = resolve_fn(&lua.globals(), func)?;
func.call(serialize(lua, &nodes)?)?;
Ok(())
}
#[cfg(test)]
mod tests {

@ -32,15 +32,15 @@ pub fn get_tty() -> Result<fs::File> {
}
}
fn call_lua(
fn call_lua_heavy(
app: &app::App,
lua: &mlua::Lua,
func: &str,
_silent: bool,
) -> Result<Option<Vec<app::ExternalMsg>>> {
let arg = app.to_lua_arg();
let arg = app.to_lua_ctx_heavy();
let arg = lua::serialize(lua, &arg)?;
lua::call_with_cache(lua, func, arg)
lua::call(lua, func, arg)
}
fn call(app: &app::App, cmd: app::Command, silent: bool) -> Result<ExitStatus> {
@ -273,10 +273,6 @@ impl Runner {
tx_msg_in.send(task)?;
}
CacheDirectoryNodes(nodes) => {
lua::cache_directory_nodes(&lua, &nodes)?;
}
Quit => {
result = Ok(None);
break 'outer;
@ -455,7 +451,7 @@ impl Runner {
}
CallLuaSilently(func) => {
match call_lua(&app, &lua, &func, false) {
match call_lua_heavy(&app, &lua, &func, false) {
Ok(Some(msgs)) => {
app = app
.handle_batch_external_msgs(msgs)?;
@ -513,7 +509,7 @@ impl Runner {
term::disable_raw_mode()?;
terminal.show_cursor()?;
match call_lua(&app, &lua, &func, false) {
match call_lua_heavy(&app, &lua, &func, false) {
Ok(Some(msgs)) => {
app = app
.handle_batch_external_msgs(msgs)?;

@ -1032,14 +1032,14 @@ pub fn draw_custom_content<B: Backend>(
ContentBody::DynamicParagraph { render } => {
let ctx = ContentRendererArg {
app: app.to_lua_arg(),
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: screen_size.into(),
};
let render = lua::serialize(lua, &ctx)
.map(|arg| {
lua::call_with_cache(lua, &render, arg)
lua::call(lua, &render, arg)
.unwrap_or_else(|e| format!("{:?}", e))
})
.unwrap_or_else(|e| e.to_string());
@ -1073,14 +1073,14 @@ pub fn draw_custom_content<B: Backend>(
ContentBody::DynamicList { render } => {
let ctx = ContentRendererArg {
app: app.to_lua_arg(),
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: screen_size.into(),
};
let items = lua::serialize(lua, &ctx)
.map(|arg| {
lua::call_with_cache(lua, &render, arg)
lua::call(lua, &render, arg)
.unwrap_or_else(|e| vec![format!("{:?}", e)])
})
.unwrap_or_else(|e| vec![e.to_string()])
@ -1142,14 +1142,14 @@ pub fn draw_custom_content<B: Backend>(
render,
} => {
let ctx = ContentRendererArg {
app: app.to_lua_arg(),
app: app.to_lua_ctx_light(),
layout_size: layout_size.into(),
screen_size: screen_size.into(),
};
let rows = lua::serialize(lua, &ctx)
.map(|arg| {
lua::call_with_cache(lua, &render, arg)
lua::call(lua, &render, arg)
.unwrap_or_else(|e| vec![vec![format!("{:?}", e)]])
})
.unwrap_or_else(|e| vec![vec![e.to_string()]])
@ -1208,7 +1208,7 @@ impl From<TuiRect> for Rect {
#[derive(Debug, Clone, Serialize)]
pub struct ContentRendererArg {
app: app::CallLuaArg,
app: app::LuaContextLight,
screen_size: Rect,
layout_size: Rect,
}

Loading…
Cancel
Save