Skip to content

Commit faac2dd

Browse files
committed
perf(cache): cache loadfile and no find modpaths without package.loaders
1 parent 32f2b71 commit faac2dd

File tree

1 file changed

+61
-51
lines changed

1 file changed

+61
-51
lines changed

lua/lazy/core/cache.lua

+61-51
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ local cache_hash
2626
---@alias CacheEntry {hash:CacheHash, modpath:string, chunk:string, used:number}
2727
---@type table<string,CacheEntry?>
2828
M.cache = {}
29-
M.loader_idx = 2 -- 2 so preload still works
3029
M.enabled = true
3130
M.ttl = 3600 * 24 * 5 -- keep unused modules for up to 5 days
3231
---@type string[]
3332
M.rtp = nil
33+
-- selene:allow(global_usage)
34+
M._loadfile = _G.loadfile
3435

3536
-- checks wether the cached modpath is still valid
3637
function M.check_path(modname, modpath)
@@ -65,9 +66,14 @@ function M.disable()
6566
if not M.enabled then
6667
return
6768
end
68-
local idx = M.idx()
69-
if idx then
70-
table.remove(package.loaders, idx)
69+
-- selene:allow(global_usage)
70+
_G.loadfile = M._loadfile
71+
---@diagnostic disable-next-line: no-unknown
72+
for i, loader in ipairs(package.loaders) do
73+
if loader == M.loader then
74+
table.remove(package.loaders, i)
75+
break
76+
end
7177
end
7278
M.enabled = false
7379
end
@@ -79,82 +85,82 @@ function M.loader(modname)
7985

8086
local chunk, err
8187
if entry and M.check_path(modname, entry.modpath) then
82-
entry.used = os.time()
83-
local hash = M.hash(entry.modpath)
84-
if not hash then
85-
return
88+
chunk, err = M.load(modname, entry.modpath)
89+
else
90+
-- find the modpath and load the module
91+
local modpath = M.find(modname)
92+
if modpath then
93+
chunk, err = M.load(modname, modpath)
8694
end
95+
end
96+
return chunk or (err and error(err)) or "not found in lazy cache"
97+
end
98+
99+
---@param modpath string
100+
---@return any, string?
101+
function M.loadfile(modpath)
102+
return M.load(modpath, modpath)
103+
end
104+
105+
---@param modkey string
106+
---@param modpath string
107+
---@return function?, string? error_message
108+
function M.load(modkey, modpath)
109+
local hash = M.hash(modpath)
110+
if not hash then
111+
-- trigger correct error
112+
return M._loadfile(modpath)
113+
end
114+
115+
local entry = M.cache[modkey]
116+
if entry then
117+
entry.used = os.time()
87118
if M.eq(entry.hash, hash) then
88119
-- found in cache and up to date
89-
chunk, err = load(entry.chunk --[[@as string]], "@" .. entry.modpath)
90-
return chunk or error(err)
120+
return loadstring(entry.chunk --[[@as string]], "@" .. entry.modpath)
91121
end
92-
-- reload from file
93-
entry.hash = hash
94-
chunk, err = loadfile(entry.modpath)
95122
else
96-
-- load the module and find its modpath
97-
local modpath
98-
chunk, modpath = M.find(modname)
99-
if modpath then
100-
entry = { hash = M.hash(modpath), modpath = modpath, used = os.time() }
101-
M.cache[modname] = entry
102-
end
123+
entry = { hash = hash, modpath = modpath, used = os.time() }
124+
M.cache[modkey] = entry
103125
end
126+
entry.hash = hash
127+
104128
if M.debug then
105129
vim.schedule(function()
106-
vim.notify("[cache:load] " .. modname)
130+
vim.notify("[cache:load] " .. modpath)
107131
end)
108132
end
109-
if entry and chunk then
133+
134+
local chunk, err = M._loadfile(entry.modpath)
135+
if chunk then
110136
M.dirty = true
111137
entry.chunk = string.dump(chunk)
112138
end
113-
return chunk or error(err)
139+
return chunk, err
114140
end
115141

116142
function M.require(modname)
117143
return M.loader(modname)()
118144
end
119145

120-
function M.idx()
121-
-- update our loader position if needed
122-
if package.loaders[M.loader_idx] ~= M.loader then
123-
M.loader_idx = nil
124-
---@diagnostic disable-next-line: no-unknown
125-
for i, loader in ipairs(package.loaders) do
126-
if loader == M.loader then
127-
M.loader_idx = i
128-
break
129-
end
130-
end
131-
end
132-
return M.loader_idx
133-
end
134-
135146
---@param modname string
147+
---@return string?
136148
function M.find(modname)
137-
if M.idx() then
138-
-- find the module and its modpath
139-
for i = M.loader_idx + 1, #package.loaders do
140-
---@diagnostic disable-next-line: no-unknown
141-
local chunk = package.loaders[i](modname)
142-
if type(chunk) == "function" then
143-
local info = debug.getinfo(chunk, "S")
144-
return chunk, (info.what ~= "C" and info.source:sub(2))
145-
end
146-
end
147-
end
149+
local basename = modname:gsub("%.", "/")
150+
local paths = { "lua/" .. basename .. ".lua", "lua/" .. basename .. "/init.lua" }
151+
return vim.api.nvim__get_runtime(paths, false, { is_lua = true })[1]
148152
end
149153

154+
-- returns the cached RTP excluding plugin dirs
150155
function M.get_rtp()
151156
if not M.rtp then
152157
M.rtp = {}
158+
---@type table<string,true>
153159
local skip = {}
154160
-- only skip plugins once Config has been setup
155161
if package.loaded["lazy.core.config"] then
156162
local Config = require("lazy.core.config")
157-
for _, plugin in ipairs(Config.plugins) do
163+
for _, plugin in pairs(Config.plugins) do
158164
if plugin.name ~= "lazy.nvim" then
159165
skip[plugin.dir] = true
160166
end
@@ -173,14 +179,18 @@ end
173179
function M.setup(opts)
174180
-- no fancy deep extend here. just set the options
175181
if opts and opts.performance and opts.performance.cache then
182+
---@diagnostic disable-next-line: no-unknown
176183
for k, v in pairs(opts.performance.cache) do
184+
---@diagnostic disable-next-line: no-unknown
177185
M.config[k] = v
178186
end
179187
end
180188
M.debug = opts and opts.debug
181189

182190
M.load_cache()
183-
table.insert(package.loaders, M.loader_idx, M.loader)
191+
table.insert(package.loaders, 2, M.loader)
192+
-- selene:allow(global_usage)
193+
_G.loadfile = M.loadfile
184194

185195
-- reset rtp when it changes
186196
vim.api.nvim_create_autocmd("OptionSet", {

0 commit comments

Comments
 (0)