perf(cache): cache loadfile and no find modpaths without package.loaders

This commit is contained in:
Folke Lemaitre 2022-12-19 13:34:37 +01:00
parent 32f2b71ff8
commit faac2dd11c
No known key found for this signature in database
GPG Key ID: 41F8B1FBACAE2040
1 changed files with 61 additions and 51 deletions

View File

@ -26,11 +26,12 @@ local cache_hash
---@alias CacheEntry {hash:CacheHash, modpath:string, chunk:string, used:number} ---@alias CacheEntry {hash:CacheHash, modpath:string, chunk:string, used:number}
---@type table<string,CacheEntry?> ---@type table<string,CacheEntry?>
M.cache = {} M.cache = {}
M.loader_idx = 2 -- 2 so preload still works
M.enabled = true M.enabled = true
M.ttl = 3600 * 24 * 5 -- keep unused modules for up to 5 days M.ttl = 3600 * 24 * 5 -- keep unused modules for up to 5 days
---@type string[] ---@type string[]
M.rtp = nil M.rtp = nil
-- selene:allow(global_usage)
M._loadfile = _G.loadfile
-- checks wether the cached modpath is still valid -- checks wether the cached modpath is still valid
function M.check_path(modname, modpath) function M.check_path(modname, modpath)
@ -65,9 +66,14 @@ function M.disable()
if not M.enabled then if not M.enabled then
return return
end end
local idx = M.idx() -- selene:allow(global_usage)
if idx then _G.loadfile = M._loadfile
table.remove(package.loaders, idx) ---@diagnostic disable-next-line: no-unknown
for i, loader in ipairs(package.loaders) do
if loader == M.loader then
table.remove(package.loaders, i)
break
end
end end
M.enabled = false M.enabled = false
end end
@ -79,82 +85,82 @@ function M.loader(modname)
local chunk, err local chunk, err
if entry and M.check_path(modname, entry.modpath) then if entry and M.check_path(modname, entry.modpath) then
entry.used = os.time() chunk, err = M.load(modname, entry.modpath)
local hash = M.hash(entry.modpath) else
if not hash then -- find the modpath and load the module
return local modpath = M.find(modname)
if modpath then
chunk, err = M.load(modname, modpath)
end end
end
return chunk or (err and error(err)) or "not found in lazy cache"
end
---@param modpath string
---@return any, string?
function M.loadfile(modpath)
return M.load(modpath, modpath)
end
---@param modkey string
---@param modpath string
---@return function?, string? error_message
function M.load(modkey, modpath)
local hash = M.hash(modpath)
if not hash then
-- trigger correct error
return M._loadfile(modpath)
end
local entry = M.cache[modkey]
if entry then
entry.used = os.time()
if M.eq(entry.hash, hash) then if M.eq(entry.hash, hash) then
-- found in cache and up to date -- found in cache and up to date
chunk, err = load(entry.chunk --[[@as string]], "@" .. entry.modpath) return loadstring(entry.chunk --[[@as string]], "@" .. entry.modpath)
return chunk or error(err)
end end
-- reload from file
entry.hash = hash
chunk, err = loadfile(entry.modpath)
else else
-- load the module and find its modpath entry = { hash = hash, modpath = modpath, used = os.time() }
local modpath M.cache[modkey] = entry
chunk, modpath = M.find(modname)
if modpath then
entry = { hash = M.hash(modpath), modpath = modpath, used = os.time() }
M.cache[modname] = entry
end
end end
entry.hash = hash
if M.debug then if M.debug then
vim.schedule(function() vim.schedule(function()
vim.notify("[cache:load] " .. modname) vim.notify("[cache:load] " .. modpath)
end) end)
end end
if entry and chunk then
local chunk, err = M._loadfile(entry.modpath)
if chunk then
M.dirty = true M.dirty = true
entry.chunk = string.dump(chunk) entry.chunk = string.dump(chunk)
end end
return chunk or error(err) return chunk, err
end end
function M.require(modname) function M.require(modname)
return M.loader(modname)() return M.loader(modname)()
end end
function M.idx()
-- update our loader position if needed
if package.loaders[M.loader_idx] ~= M.loader then
M.loader_idx = nil
---@diagnostic disable-next-line: no-unknown
for i, loader in ipairs(package.loaders) do
if loader == M.loader then
M.loader_idx = i
break
end
end
end
return M.loader_idx
end
---@param modname string ---@param modname string
---@return string?
function M.find(modname) function M.find(modname)
if M.idx() then local basename = modname:gsub("%.", "/")
-- find the module and its modpath local paths = { "lua/" .. basename .. ".lua", "lua/" .. basename .. "/init.lua" }
for i = M.loader_idx + 1, #package.loaders do return vim.api.nvim__get_runtime(paths, false, { is_lua = true })[1]
---@diagnostic disable-next-line: no-unknown
local chunk = package.loaders[i](modname)
if type(chunk) == "function" then
local info = debug.getinfo(chunk, "S")
return chunk, (info.what ~= "C" and info.source:sub(2))
end
end
end
end end
-- returns the cached RTP excluding plugin dirs
function M.get_rtp() function M.get_rtp()
if not M.rtp then if not M.rtp then
M.rtp = {} M.rtp = {}
---@type table<string,true>
local skip = {} local skip = {}
-- only skip plugins once Config has been setup -- only skip plugins once Config has been setup
if package.loaded["lazy.core.config"] then if package.loaded["lazy.core.config"] then
local Config = require("lazy.core.config") local Config = require("lazy.core.config")
for _, plugin in ipairs(Config.plugins) do for _, plugin in pairs(Config.plugins) do
if plugin.name ~= "lazy.nvim" then if plugin.name ~= "lazy.nvim" then
skip[plugin.dir] = true skip[plugin.dir] = true
end end
@ -173,14 +179,18 @@ end
function M.setup(opts) function M.setup(opts)
-- no fancy deep extend here. just set the options -- no fancy deep extend here. just set the options
if opts and opts.performance and opts.performance.cache then if opts and opts.performance and opts.performance.cache then
---@diagnostic disable-next-line: no-unknown
for k, v in pairs(opts.performance.cache) do for k, v in pairs(opts.performance.cache) do
---@diagnostic disable-next-line: no-unknown
M.config[k] = v M.config[k] = v
end end
end end
M.debug = opts and opts.debug M.debug = opts and opts.debug
M.load_cache() M.load_cache()
table.insert(package.loaders, M.loader_idx, M.loader) table.insert(package.loaders, 2, M.loader)
-- selene:allow(global_usage)
_G.loadfile = M.loadfile
-- reset rtp when it changes -- reset rtp when it changes
vim.api.nvim_create_autocmd("OptionSet", { vim.api.nvim_create_autocmd("OptionSet", {