Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Lua API performance by caching #413

Merged
merged 4 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/__cache__.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- 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
45 changes: 42 additions & 3 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,7 @@ pub struct Command {

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum MsgOut {
CacheDirectoryNodes(Vec<Node>),
ExplorePwdAsync,
ExploreParentsAsync,
Refresh,
Expand Down Expand Up @@ -1246,12 +1247,29 @@ impl History {
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[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 version: String,
pub pwd: String,
pub focused_node: Option<Node>,
pub directory_buffer: Option<DirectoryBuffer>,
pub directory_buffer: Option<CachedDirectoryBuffer>,
pub selection: IndexSet<Node>,
pub mode: Mode,
pub layout: Layout,
Expand Down Expand Up @@ -1434,6 +1452,22 @@ impl App {
self
}

pub fn handle_batch_external_msgs(
mut self,
msgs: Vec<ExternalMsg>,
) -> Result<Self> {
for task in msgs
.into_iter()
.map(|msg| Task::new(MsgIn::External(msg), None))
{
self = match task.msg {
MsgIn::Internal(msg) => self.handle_internal(msg)?,
MsgIn::External(msg) => self.handle_external(msg, task.key)?,
};
}
self.refresh()
}

pub fn handle_task(self, task: Task) -> Result<Self> {
let app = match task.msg {
MsgIn::Internal(msg) => self.handle_internal(msg)?,
Expand Down Expand Up @@ -2246,6 +2280,8 @@ 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)
Expand Down Expand Up @@ -2741,7 +2777,10 @@ impl App {
version: self.version.clone(),
pwd: self.pwd.clone(),
focused_node: self.focused_node().cloned(),
directory_buffer: self.directory_buffer.clone(),
directory_buffer: self
.directory_buffer
.as_ref()
.map(|buf| CachedDirectoryBuffer::new(buf)),
selection: self.selection.clone(),
mode: self.mode.clone(),
layout: self.layout.clone(),
Expand Down
81 changes: 53 additions & 28 deletions src/lua.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::app::Node;
use crate::app::VERSION;
use crate::config::Config;
use anyhow::bail;
Expand All @@ -8,6 +9,7 @@ use serde::Deserialize;
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";

fn parse_version(version: &str) -> Result<(u16, u16, u16, Option<u16>)> {
Expand Down Expand Up @@ -49,30 +51,6 @@ pub fn check_version(version: &str, path: &str) -> Result<()> {
}
}

fn resolve_fn_recursive<'lua, 'a>(
table: &mlua::Table<'lua>,
mut path: impl Iterator<Item = &'a str>,
) -> Result<mlua::Function<'lua>> {
if let Some(nxt) = path.next() {
match table.get(nxt)? {
mlua::Value::Table(t) => resolve_fn_recursive(&t, path),
mlua::Value::Function(f) => Ok(f),
t => bail!("{:?} is not a function", t),
}
} else {
bail!("Invalid path")
}
}

/// This function resolves paths like `builtin.func_foo`, `custom.func_bar` into lua functions.
pub fn resolve_fn<'lua>(
globals: &mlua::Table<'lua>,
path: &str,
) -> Result<mlua::Function<'lua>> {
let path = format!("xplr.fn.{}", path);
resolve_fn_recursive(globals, path.split('.'))
}

/// Used to initialize Lua globals
pub fn init(lua: &Lua) -> Result<Config> {
let config = Config::default();
Expand All @@ -91,6 +69,7 @@ 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")?)?;
Expand Down Expand Up @@ -119,18 +98,64 @@ pub fn extend(lua: &Lua, path: &str) -> Result<Config> {
Ok(config)
}

/// Used to call lua functions.
fn resolve_fn_recursive<'lua, 'a>(
table: &mlua::Table<'lua>,
mut path: impl Iterator<Item = &'a str>,
) -> Result<mlua::Function<'lua>> {
if let Some(nxt) = path.next() {
match table.get(nxt)? {
mlua::Value::Table(t) => resolve_fn_recursive(&t, path),
mlua::Value::Function(f) => Ok(f),
t => bail!("{:?} is not a function", t),
}
} else {
bail!("Invalid path")
}
}

/// This function resolves paths like `builtin.func_foo`, `custom.func_bar` into lua functions.
pub fn resolve_fn<'lua>(
globals: &mlua::Table<'lua>,
path: &str,
) -> Result<mlua::Function<'lua>> {
resolve_fn_recursive(globals, path.split('.'))
}

pub fn call<'lua, R: Deserialize<'lua>>(
lua: &'lua Lua,
func: &str,
args: mlua::Value<'lua>,
arg: mlua::Value<'lua>,
) -> Result<R> {
let func = resolve_fn(&lua.globals(), func)?;
let res: mlua::Value = func.call(args)?;
let func = format!("xplr.fn.{}", func);
let func = resolve_fn(&lua.globals(), &func)?;
let res: mlua::Value = func.call(arg)?;
let res: R = lua.from_value(res)?;
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(lua.to_value(nodes)?)?;
Ok(())
}

#[cfg(test)]
mod tests {

Expand Down
17 changes: 11 additions & 6 deletions src/pwd_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ pub fn keep_watching(
thread::spawn(move || loop {
if let Ok(new_pwd) = rx_pwd_watcher.try_recv() {
pwd = PathBuf::from(new_pwd);
last_modified = pwd
.metadata()
.and_then(|m| m.modified())
.unwrap_or(last_modified)
} else if let Err(e) = pwd
.metadata()
.map_err(Error::new)
.and_then(|m| m.modified().map_err(Error::new))
.and_then(|modified| {
if modified != last_modified {
let msg = MsgIn::External(ExternalMsg::ExplorePwdAsync);
last_modified = modified;
let msg = MsgIn::External(ExternalMsg::ExplorePwdAsync);
tx_msg_in.send(Task::new(msg, None)).map_err(Error::new)
} else {
thread::sleep(Duration::from_secs(1));
Expand All @@ -48,17 +52,18 @@ mod tests {
#[test]
fn test_pwd_watcher() {
let (tx_msg_in, rx_msg_in) = mpsc::channel();
let (tx_pwd_watcher, rx_pwd_watcher) = mpsc::channel();
let (_, rx_pwd_watcher) = mpsc::channel();

let result = keep_watching("/", tx_msg_in, rx_pwd_watcher);
let result = keep_watching("/tmp", tx_msg_in, rx_pwd_watcher);

assert!(result.is_ok());

tx_pwd_watcher.send("/bin".to_string()).unwrap();
let task = rx_msg_in.recv().unwrap();
let file = "/tmp/__xplr_pwd_watcher_test__";
std::fs::write(file, "test").unwrap();
std::fs::remove_file(file).unwrap();

let task = rx_msg_in.recv().unwrap();
let msg = MsgIn::External(ExternalMsg::ExplorePwdAsync);

assert_eq!(task, Task::new(msg, None));
}
}
Loading