From c5808a0db43f8924b014b1f4c3653683a207fa57 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: Mon, 17 Mar 2025 16:12:43 +0800 Subject: [PATCH] perf: lazy compile and cache lua plugins as binary bytecode (#2490) --- scripts/validate-form/main.js | 2 +- yazi-fm/src/logs.rs | 2 +- yazi-plugin/src/isolate/entry.rs | 9 ++------ yazi-plugin/src/isolate/fetch.rs | 8 ++----- yazi-plugin/src/isolate/peek.rs | 9 ++------ yazi-plugin/src/isolate/preload.rs | 8 ++----- yazi-plugin/src/isolate/spot.rs | 10 +++------ yazi-plugin/src/loader/chunk.rs | 20 +++++++++++++----- yazi-plugin/src/loader/loader.rs | 34 +++++++++++++++++++++++------- 9 files changed, 54 insertions(+), 48 deletions(-) diff --git a/scripts/validate-form/main.js b/scripts/validate-form/main.js index ecbfff02..d7cc3c82 100644 --- a/scripts/validate-form/main.js +++ b/scripts/validate-form/main.js @@ -4,7 +4,7 @@ const RE_DEPENDENCIES = /Dependencies\s+[/a-z]+\s*:\s/gm const RE_CHECKLIST = /#{3}\s+Checklist\s+(?:^-\s+\[x]\s+.+?(?:\n|\r\n|$)){2}/gm function bugReportBody(creator, content, hash) { - if (RE_CHECKLIST.test(content) && new RegExp(` \\(${hash}[a-z]? `).test(content)) { + if (RE_DEPENDENCIES.test(content) && RE_CHECKLIST.test(content) && new RegExp(` \\(${hash}[a-z]? `).test(content)) { return null } diff --git a/yazi-fm/src/logs.rs b/yazi-fm/src/logs.rs index 1ba2d68d..7e4be580 100644 --- a/yazi-fm/src/logs.rs +++ b/yazi-fm/src/logs.rs @@ -14,7 +14,7 @@ pub(super) struct Logs; impl Logs { pub(super) fn start() -> anyhow::Result<()> { let level = LOG_LEVEL.get(); - if LOG_LEVEL.get().is_none() { + if level.is_none() { return Ok(()); } diff --git a/yazi-plugin/src/isolate/entry.rs b/yazi-plugin/src/isolate/entry.rs index 6a112fbf..831cbb05 100644 --- a/yazi-plugin/src/isolate/entry.rs +++ b/yazi-plugin/src/isolate/entry.rs @@ -1,4 +1,4 @@ -use mlua::{ExternalError, ExternalResult, ObjectLike, Table}; +use mlua::{ExternalResult, ObjectLike}; use tokio::runtime::Handle; use yazi_dds::Sendable; use yazi_proxy::options::PluginOpt; @@ -11,14 +11,9 @@ pub async fn entry(opt: PluginOpt) -> mlua::Result<()> { tokio::task::spawn_blocking(move || { let lua = slim_lua(&opt.id)?; - let plugin: Table = if let Some(c) = LOADER.read().get(opt.id.as_ref()) { - lua.load(c.as_bytes()).set_name(opt.id.as_ref()).call(())? - } else { - return Err("unloaded plugin".into_lua_err()); - }; + let plugin = LOADER.load_once(&lua, &opt.id)?; let job = lua.create_table_from([("args", Sendable::args_to_table(&lua, opt.args)?)])?; - Handle::current().block_on(plugin.call_async_method("entry", job)) }) .await diff --git a/yazi-plugin/src/isolate/fetch.rs b/yazi-plugin/src/isolate/fetch.rs index 923b9180..063d2f7f 100644 --- a/yazi-plugin/src/isolate/fetch.rs +++ b/yazi-plugin/src/isolate/fetch.rs @@ -1,4 +1,4 @@ -use mlua::{ExternalError, ExternalResult, FromLua, IntoLua, Lua, ObjectLike, Table, Value}; +use mlua::{ExternalResult, FromLua, IntoLua, Lua, ObjectLike, Value}; use tokio::runtime::Handle; use yazi_dds::Sendable; use yazi_shared::event::CmdCow; @@ -17,11 +17,7 @@ pub async fn fetch( tokio::task::spawn_blocking(move || { let lua = slim_lua(&cmd.name)?; - let plugin: Table = if let Some(c) = LOADER.read().get(&cmd.name) { - lua.load(c.as_bytes()).set_name(&cmd.name).call(())? - } else { - return Err("unloaded plugin".into_lua_err()); - }; + let plugin = LOADER.load_once(&lua, &cmd.name)?; Handle::current().block_on(plugin.call_async_method( "fetch", diff --git a/yazi-plugin/src/isolate/peek.rs b/yazi-plugin/src/isolate/peek.rs index c922d3c1..308e9a38 100644 --- a/yazi-plugin/src/isolate/peek.rs +++ b/yazi-plugin/src/isolate/peek.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use mlua::{ExternalError, HookTriggers, IntoLua, ObjectLike, Table, VmState}; +use mlua::{ExternalError, HookTriggers, IntoLua, ObjectLike, VmState}; use tokio::{runtime::Handle, select}; use tokio_util::sync::CancellationToken; use tracing::error; @@ -84,12 +84,7 @@ fn peek_async( }, ); - let plugin: Table = if let Some(c) = LOADER.read().get(&cmd.name) { - lua.load(c.as_bytes()).set_name(&cmd.name).call(())? - } else { - return Err("unloaded plugin".into_lua_err()); - }; - + let plugin = LOADER.load_once(&lua, &cmd.name)?; let job = lua.create_table_from([ ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), ("args", Sendable::args_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), diff --git a/yazi-plugin/src/isolate/preload.rs b/yazi-plugin/src/isolate/preload.rs index c519bc08..3bcaf596 100644 --- a/yazi-plugin/src/isolate/preload.rs +++ b/yazi-plugin/src/isolate/preload.rs @@ -1,4 +1,4 @@ -use mlua::{ExternalError, ExternalResult, IntoLua, ObjectLike, Table}; +use mlua::{ExternalResult, IntoLua, ObjectLike}; use tokio::runtime::Handle; use yazi_config::LAYOUT; use yazi_dds::Sendable; @@ -15,11 +15,7 @@ pub async fn preload( tokio::task::spawn_blocking(move || { let lua = slim_lua(&cmd.name)?; - let plugin: Table = if let Some(b) = LOADER.read().get(&cmd.name) { - lua.load(b.as_bytes()).set_name(&cmd.name).call(())? - } else { - return Err("unloaded plugin".into_lua_err()); - }; + let plugin = LOADER.load_once(&lua, &cmd.name)?; let job = lua.create_table_from([ ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), diff --git a/yazi-plugin/src/isolate/spot.rs b/yazi-plugin/src/isolate/spot.rs index d7a2ba8e..c3040085 100644 --- a/yazi-plugin/src/isolate/spot.rs +++ b/yazi-plugin/src/isolate/spot.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, ObjectLike, Table, VmState}; +use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, ObjectLike, VmState}; use tokio::{runtime::Handle, select}; use tokio_util::sync::CancellationToken; use tracing::error; @@ -35,18 +35,14 @@ pub fn spot( }, ); - let plugin: Table = if let Some(b) = LOADER.read().get(&cmd.name) { - lua.load(b.as_bytes()).set_name(&cmd.name).call(())? - } else { - return Err("unloaded plugin".into_lua_err()); - }; - + let plugin = LOADER.load_once(&lua, &cmd.name)?; let job = lua.create_table_from([ ("args", Sendable::args_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), ("file", File(file).into_lua(&lua)?), ("mime", mime.into_lua(&lua)?), ("skip", skip.into_lua(&lua)?), ])?; + if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("spot", job).await } }; diff --git a/yazi-plugin/src/loader/chunk.rs b/yazi-plugin/src/loader/chunk.rs index 4c0b3718..c84b43af 100644 --- a/yazi-plugin/src/loader/chunk.rs +++ b/yazi-plugin/src/loader/chunk.rs @@ -1,8 +1,10 @@ use std::borrow::Cow; +use mlua::{AsChunk, ChunkMode}; use yazi_shared::natsort; pub struct Chunk { + pub mode: ChunkMode, pub bytes: Cow<'static, [u8]>, pub since: String, pub sync_peek: bool, @@ -10,9 +12,6 @@ pub struct Chunk { } impl Chunk { - #[inline] - pub fn as_bytes(&self) -> &[u8] { &self.bytes } - #[inline] pub fn compatible(&self) -> bool { let s = yazi_boot::actions::Actions::version(); @@ -46,8 +45,13 @@ impl Chunk { impl From> for Chunk { fn from(b: Cow<'static, [u8]>) -> Self { - let mut chunk = - Self { bytes: b, since: String::new(), sync_entry: false, sync_peek: false }; + let mut chunk = Self { + mode: ChunkMode::Text, + bytes: b, + since: String::new(), + sync_entry: false, + sync_peek: false, + }; chunk.analyze(); chunk } @@ -60,3 +64,9 @@ impl From<&'static [u8]> for Chunk { impl From> for Chunk { fn from(b: Vec) -> Self { Self::from(Cow::Owned(b)) } } + +impl<'a> AsChunk<'a> for &'a Chunk { + fn source(self) -> std::io::Result> { Ok(Cow::Borrowed(&self.bytes)) } + + fn mode(&self) -> Option { Some(self.mode) } +} diff --git a/yazi-plugin/src/loader/loader.rs b/yazi-plugin/src/loader/loader.rs index a1d83c5d..7caf4564 100644 --- a/yazi-plugin/src/loader/loader.rs +++ b/yazi-plugin/src/loader/loader.rs @@ -1,7 +1,7 @@ -use std::{collections::HashMap, ops::Deref}; +use std::{borrow::Cow, collections::HashMap, ops::Deref}; use anyhow::{Context, Result, bail}; -use mlua::{ExternalError, Lua, Table}; +use mlua::{ChunkMode, ExternalError, Lua, Table}; use parking_lot::RwLock; use tokio::fs; use yazi_boot::BOOT; @@ -75,16 +75,34 @@ impl Loader { return Ok(t); } - let t: Table = match self.read().get(id) { - Some(c) => lua.load(c.as_bytes()).set_name(id).call(())?, - None => Err(format!("plugin `{id}` not found").into_lua_err())?, - }; - + let t = self.load_once(lua, id)?; t.raw_set("_id", lua.create_string(id)?)?; + loaded.raw_set(id, t.clone())?; Ok(t) } + pub fn load_once(&self, lua: &Lua, id: &str) -> mlua::Result { + let mut mode = ChunkMode::Text; + let f = match self.read().get(id) { + Some(c) => { + mode = c.mode; + lua.load(c).set_name(id).into_function() + } + None => Err(format!("plugin `{id}` not found").into_lua_err()), + }?; + + if mode != ChunkMode::Binary { + let b = f.dump(true); + if let Some(c) = self.write().get_mut(id) { + c.mode = ChunkMode::Binary; + c.bytes = Cow::Owned(b); + } + } + + f.call(()) + } + pub fn try_load(&self, lua: &Lua, id: &str) -> mlua::Result
{ lua.globals().raw_get::
("package")?.raw_get::
("loaded")?.raw_get(id) } @@ -95,7 +113,7 @@ impl Loader { return Ok(t); } - let t: Table = lua.load(chunk.as_bytes()).set_name(id).call(())?; + let t: Table = lua.load(chunk).set_name(id).call(())?; t.raw_set("_id", lua.create_string(id)?)?; loaded.raw_set(id, t.clone())?;