mirror of
https://github.com/folke/lazy.nvim.git
synced 2025-04-18 20:36:45 +00:00
perf: split caching in state, cache and module
This commit is contained in:
parent
a543134b8c
commit
54d5ff18f5
8 changed files with 455 additions and 395 deletions
109
lua/lazy/core/cache.lua
Normal file
109
lua/lazy/core/cache.lua
Normal file
|
@ -0,0 +1,109 @@
|
|||
-- Simple string cache with fast saving and loading from file
|
||||
local M = {}
|
||||
|
||||
local cache_path = vim.fn.stdpath("state") .. "/lazy/plugins.state"
|
||||
---@type string
|
||||
local cache_hash = nil
|
||||
local dirty = false
|
||||
|
||||
---@type table<string,boolean>
|
||||
local used = {}
|
||||
|
||||
---@type table<string,string>
|
||||
local cache = {}
|
||||
|
||||
function M.get(key)
|
||||
if cache[key] then
|
||||
used[key] = true
|
||||
return cache[key]
|
||||
end
|
||||
end
|
||||
|
||||
function M.set(key, value)
|
||||
cache[key] = value
|
||||
used[key] = true
|
||||
dirty = true
|
||||
end
|
||||
|
||||
function M.del(key)
|
||||
cache[key] = nil
|
||||
dirty = true
|
||||
end
|
||||
|
||||
function M.dirty()
|
||||
dirty = true
|
||||
end
|
||||
|
||||
function M.use(pattern)
|
||||
for key, _ in pairs(cache) do
|
||||
if key:find(pattern) then
|
||||
used[key] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.hash(file)
|
||||
local stat = vim.loop.fs_stat(file)
|
||||
return stat and (stat.mtime.sec .. stat.mtime.nsec .. stat.size)
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
M.load()
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = "LazyDone",
|
||||
once = true,
|
||||
callback = function()
|
||||
vim.api.nvim_create_autocmd("VimLeavePre", {
|
||||
callback = function()
|
||||
if dirty then
|
||||
local hash = M.hash(cache_path)
|
||||
-- abort when the file was changed in the meantime
|
||||
if hash == nil or cache_hash == hash then
|
||||
M.save()
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function M.save()
|
||||
require("lazy.core.state").save()
|
||||
require("lazy.core.module").save()
|
||||
|
||||
vim.fn.mkdir(vim.fn.fnamemodify(cache_path, ":p:h"), "p")
|
||||
local f = assert(io.open(cache_path, "wb"))
|
||||
for key, value in pairs(cache) do
|
||||
if used[key] then
|
||||
f:write(key, "\0", tostring(#value), "\0", value)
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
end
|
||||
|
||||
function M.load()
|
||||
cache = {}
|
||||
local f = io.open(cache_path, "rb")
|
||||
if f then
|
||||
cache_hash = M.hash(cache_path)
|
||||
---@type string
|
||||
local data = f:read("*a")
|
||||
f:close()
|
||||
|
||||
local from = 1
|
||||
local to = data:find("\0", from, true)
|
||||
while to do
|
||||
local key = data:sub(from, to - 1)
|
||||
from = to + 1
|
||||
to = data:find("\0", from, true)
|
||||
local len = tonumber(data:sub(from, to - 1))
|
||||
from = to + 1
|
||||
cache[key] = data:sub(from, from + len - 1)
|
||||
from = from + len
|
||||
to = data:find("\0", from, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
130
lua/lazy/core/module.lua
Normal file
130
lua/lazy/core/module.lua
Normal file
|
@ -0,0 +1,130 @@
|
|||
local Cache = require("lazy.core.cache")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type table<string, {file: string, hash?:string}>
|
||||
M.modules = {}
|
||||
|
||||
function M.add(modname, file)
|
||||
if not M.modules[modname] then
|
||||
M.modules[modname] = { file = file }
|
||||
end
|
||||
end
|
||||
|
||||
---@param modname string
|
||||
function M.load(modname)
|
||||
if type(package.loaded[modname]) == "table" then
|
||||
return package.loaded[modname]
|
||||
end
|
||||
|
||||
local info = M.modules[modname]
|
||||
if info then
|
||||
local err
|
||||
---@type string|fun()|nil
|
||||
local chunk = Cache.get(modname)
|
||||
|
||||
if not chunk then
|
||||
vim.schedule(function()
|
||||
vim.notify("loading " .. modname)
|
||||
end)
|
||||
chunk, err = loadfile(info.file)
|
||||
if chunk then
|
||||
Cache.set(modname, string.dump(chunk))
|
||||
info.hash = info.hash or Cache.hash(info.file)
|
||||
end
|
||||
end
|
||||
|
||||
if type(chunk) == "string" then
|
||||
chunk, err = loadstring(chunk --[[@as string]], "@" .. info.file)
|
||||
end
|
||||
|
||||
if not chunk then
|
||||
error(err)
|
||||
end
|
||||
|
||||
---@type table
|
||||
local mod = chunk()
|
||||
package.loaded[modname] = mod
|
||||
return mod
|
||||
end
|
||||
end
|
||||
|
||||
local function _add_module(dir, modname)
|
||||
local d = vim.loop.fs_opendir(dir, nil, 100)
|
||||
if d then
|
||||
---@type {name: string, type: "file"|"directory"|"link"}[]
|
||||
local entries = vim.loop.fs_readdir(d)
|
||||
while entries do
|
||||
for _, entry in ipairs(entries) do
|
||||
local path = dir .. "/" .. entry.name
|
||||
if entry.type == "directory" then
|
||||
_add_module(path, modname .. "." .. entry.name)
|
||||
else
|
||||
local childname = entry.name:match("^(.*)%.lua$")
|
||||
if childname then
|
||||
local child = entry.name == "init.lua" and modname or (modname .. "." .. childname)
|
||||
if child then
|
||||
M.add(child, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
entries = vim.loop.fs_readdir(d)
|
||||
end
|
||||
vim.loop.fs_closedir(d)
|
||||
end
|
||||
end
|
||||
|
||||
function M.add_module(path)
|
||||
---@type string
|
||||
local modname = path:match("/lua/(.*)/?")
|
||||
assert(modname)
|
||||
modname = modname:gsub("/", ".")
|
||||
if vim.loop.fs_stat(path .. ".lua") then
|
||||
M.add(modname, path .. ".lua")
|
||||
end
|
||||
_add_module(path, modname)
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
-- load cache
|
||||
local value = Cache.get("cache.modules")
|
||||
if value then
|
||||
M.modules = vim.json.decode(value)
|
||||
for k, v in pairs(M.modules) do
|
||||
if Cache.hash(v.file) ~= v.hash then
|
||||
Cache.del(k)
|
||||
M.changed = true
|
||||
M.modules[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- preload core modules
|
||||
local root = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":p:h:h")
|
||||
for _, name in ipairs({ "util", "config", "plugin", "loader", "core.state" }) do
|
||||
local modname = "lazy." .. name
|
||||
M.add(modname, root .. "/" .. name:gsub("%.", "/") .. ".lua")
|
||||
end
|
||||
|
||||
table.insert(package.loaders, 2, function(modname)
|
||||
if M.modules[modname] then
|
||||
return function()
|
||||
return M.load(modname)
|
||||
end
|
||||
end
|
||||
end)
|
||||
return M
|
||||
end
|
||||
|
||||
function M.save()
|
||||
local value = {}
|
||||
for k, v in pairs(M.modules) do
|
||||
if v.hash then
|
||||
value[k] = v
|
||||
end
|
||||
end
|
||||
Cache.set("cache.modules", vim.json.encode(value))
|
||||
end
|
||||
|
||||
return M
|
127
lua/lazy/core/state.lua
Normal file
127
lua/lazy/core/state.lua
Normal file
|
@ -0,0 +1,127 @@
|
|||
local Cache = require("lazy.core.cache")
|
||||
local Module = require("lazy.core.module")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.functions = { "init", "config", "run" }
|
||||
M.changed = true
|
||||
|
||||
function M.save()
|
||||
local Config = require("lazy.config")
|
||||
|
||||
---@class LazyState
|
||||
local state = {
|
||||
---@type LazyPlugin[]
|
||||
plugins = {},
|
||||
loaders = require("lazy.loader").loaders,
|
||||
config = Config.options,
|
||||
}
|
||||
|
||||
local skip = { installed = true, loaded = true, tasks = true, dirty = true, [1] = true, dir = true }
|
||||
local funcount = 0
|
||||
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
---@type LazyPlugin | {_chunks: string[] | table<string, number>}
|
||||
local save = {}
|
||||
table.insert(state.plugins, save)
|
||||
for k, v in pairs(plugin) do
|
||||
if type(v) == "function" then
|
||||
if vim.tbl_contains(M.functions, k) then
|
||||
if plugin.modname then
|
||||
save[k] = true
|
||||
else
|
||||
funcount = funcount + 1
|
||||
Cache.set("cache.state.fun." .. funcount, string.dump(v))
|
||||
save[k] = funcount
|
||||
end
|
||||
end
|
||||
elseif not skip[k] then
|
||||
save[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
Cache.set("cache.state", vim.json.encode(state))
|
||||
end
|
||||
|
||||
local function load_plugin(plugin, fun, ...)
|
||||
local mod = Module.load(plugin.modname)
|
||||
for k, v in pairs(mod) do
|
||||
if type(v) == "function" then
|
||||
plugin[k] = v
|
||||
end
|
||||
end
|
||||
return mod[fun](...)
|
||||
end
|
||||
|
||||
function M.load()
|
||||
---@type boolean, LazyState
|
||||
local ok, state = pcall(vim.json.decode, Cache.get("cache.state"))
|
||||
if not ok then
|
||||
Cache.dirty()
|
||||
return false
|
||||
end
|
||||
|
||||
local Util = require("lazy.util")
|
||||
local Config = require("lazy.config")
|
||||
|
||||
if not vim.deep_equal(Config.options, state.config) then
|
||||
Cache.dirty()
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check for installed plugins
|
||||
---@type table<"opt"|"start", table<string,boolean>>
|
||||
local installed = { opt = {}, start = {} }
|
||||
for _, opt in ipairs({ "opt", "start" }) do
|
||||
for _, entry in ipairs(Util.scandir(Config.options.package_path .. "/" .. opt)) do
|
||||
if entry.type == "directory" or entry.type == "link" then
|
||||
installed[opt][entry.name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- plugins
|
||||
for _, plugin in ipairs(state.plugins) do
|
||||
---@cast plugin LazyPlugin|{_chunks:table}
|
||||
Config.plugins[plugin.name] = plugin
|
||||
plugin.loaded = false
|
||||
plugin.dir = Config.options.package_path .. "/" .. (plugin.opt and "opt" or "start") .. "/" .. plugin.pack
|
||||
plugin.installed = installed[plugin.opt and "opt" or "start"][plugin.pack]
|
||||
if plugin.modname then
|
||||
-- mark module as used
|
||||
if not Cache.get(plugin.modname) then
|
||||
Util.error("Module missing for " .. plugin.name)
|
||||
end
|
||||
for _, fun in ipairs(M.functions) do
|
||||
if plugin[fun] == true then
|
||||
plugin[fun] = function(...)
|
||||
return load_plugin(plugin, fun, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
for _, fun in ipairs(M.functions) do
|
||||
if type(plugin[fun]) == "number" then
|
||||
local chunk = Cache.get("cache.state.fun." .. plugin[fun])
|
||||
if not chunk then
|
||||
Util.error("Chunk missing for " .. plugin.name)
|
||||
end
|
||||
plugin[fun] = function(...)
|
||||
plugin[fun] = loadstring(chunk)
|
||||
return plugin[fun](...)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- loaders
|
||||
local Loader = require("lazy.loader")
|
||||
Loader.loaders = state.loaders
|
||||
|
||||
M.changed = false
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return M
|
Loading…
Add table
Add a link
Reference in a new issue