diff --git a/lua/lazy/core/cache.lua b/lua/lazy/core/cache.lua deleted file mode 100644 index 8614190..0000000 --- a/lua/lazy/core/cache.lua +++ /dev/null @@ -1,96 +0,0 @@ --- Simple string cache with fast saving and loading from file -local M = {} - -M.dirty = false - -local cache_path = vim.fn.stdpath("state") .. "/lazy.state" ----@type string -local cache_hash = "" ----@type table -local used = {} ----@type table -local cache = {} - ----@return string? -function M.get(key) - if cache[key] then - used[key] = true - return cache[key] - end -end - -function M.debug() - local ret = {} - for key, value in pairs(cache) do - ret[key] = #value - end - return ret -end - -function M.set(key, value) - cache[key] = value - used[key] = true - M.dirty = true -end - -function M.del(key) - cache[key] = nil - M.dirty = true -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() - 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 - -function M.autosave() - vim.api.nvim_create_autocmd("VimLeavePre", { - callback = function() - if M.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 - -function M.save() - require("lazy.core.module").save() - - 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 - -return M diff --git a/lua/lazy/core/config.lua b/lua/lazy/core/config.lua index 641c94f..802695e 100644 --- a/lua/lazy/core/config.lua +++ b/lua/lazy/core/config.lua @@ -68,7 +68,7 @@ function M.setup(opts) pattern = "VeryLazy", once = true, callback = function() - require("lazy.core.cache").autosave() + require("lazy.core.module").autosave() require("lazy.view").setup() end, }) diff --git a/lua/lazy/core/module.lua b/lua/lazy/core/module.lua index 3eb5780..6d59254 100644 --- a/lua/lazy/core/module.lua +++ b/lua/lazy/core/module.lua @@ -1,51 +1,50 @@ -local Cache = require("lazy.core.cache") +local ffi = require("ffi") +---@diagnostic disable-next-line: no-unknown +local uv = vim.loop local M = {} +M.dirty = false ----@type table -M.hashes = {} +local cache_path = vim.fn.stdpath("state") .. "/lazy.state" +---@type CacheHash +local cache_hash + +---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number} +---@alias CacheEntry {hash:CacheHash, chunk:string, used:boolean} +---@type table +M.cache = {} ---@param modname string ---@param modpath string ---@return any function M.load(modname, modpath) - local err - ---@type (string|fun())? - local chunk = Cache.get(modname) + local entry = M.cache[modname] + local hash = assert(M.hash(modpath)) - local hash = Cache.hash(modpath) - if hash ~= M.hashes[modname] then - M.hashes[modname] = hash - Cache.del(modname) - chunk = nil + if entry and not M.eq(entry.hash, hash) then + entry = nil end - if chunk then - chunk, err = load(chunk --[[@as string]], "@" .. modpath, "b") + local chunk, err + if entry then + entry.used = true + chunk, err = load(entry.chunk --[[@as string]], "@" .. modpath, "b") else vim.schedule(function() vim.notify("loadfile(" .. modname .. ")") end) chunk, err = loadfile(modpath) - if chunk and not err then - Cache.set(modname, string.dump(chunk)) + if chunk then + M.dirty = true + M.cache[modname] = { hash = hash, chunk = string.dump(chunk), used = true } end end - if chunk then - return chunk() - else - error(err) - end + return chunk and chunk() or error(err) end function M.setup() - -- load cache - local value = Cache.get("cache.modules") - if value then - M.hashes = vim.json.decode(value) - end - + M.load_cache() -- preload core modules local root = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":p:h:h") for _, name in ipairs({ "util", "config", "loader", "plugin", "handler" }) do @@ -58,8 +57,67 @@ function M.setup() return M end -function M.save() - Cache.set("cache.modules", vim.json.encode(M.hashes)) +---@return CacheHash? +function M.hash(file) + return uv.fs_stat(file) +end + +---@param h1 CacheHash +---@param h2 CacheHash +function M.eq(h1, h2) + return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec +end + +function M.save_cache() + local f = assert(uv.fs_open(cache_path, "w", 438)) + vim.loop.fs_ftruncate(f, 0) + for modname, entry in pairs(M.cache) do + if entry.used then + entry.modname = modname + local header = { entry.hash.size, entry.hash.mtime.sec, entry.hash.mtime.nsec, #modname, #entry.chunk } + uv.fs_write(f, ffi.string(ffi.new("const uint32_t[5]", header), 20)) + uv.fs_write(f, modname) + uv.fs_write(f, entry.chunk) + end + end + uv.fs_close(f) +end + +function M.load_cache() + M.cache = {} + local f = uv.fs_open(cache_path, "r", 438) + if f then + cache_hash = uv.fs_fstat(f) --[[@as CacheHash]] + local data = uv.fs_read(f, cache_hash.size, 0) --[[@as string]] + uv.fs_close(f) + + local offset = 1 + while offset + 1 < #data do + local header = ffi.cast("uint32_t*", ffi.new("const char[20]", data:sub(offset, offset + 19))) + offset = offset + 20 + local modname = data:sub(offset, offset + header[3] - 1) + offset = offset + header[3] + local chunk = data:sub(offset, offset + header[4] - 1) + offset = offset + header[4] + M.cache[modname] = { hash = { size = header[0], mtime = { sec = header[1], nsec = header[2] } }, chunk = chunk } + end + end +end + +function M.autosave() + vim.api.nvim_create_autocmd("VimLeavePre", { + callback = function() + if M.dirty then + local hash = M.hash(cache_path) + -- abort when the file was changed in the meantime + if hash == nil or M.eq(cache_hash, hash) then + vim.fn.system("echo start >> foo.txt") + M.save_cache() + vim.fn.system("echo stop >> foo.txt") + end + end + end, + }) end return M diff --git a/lua/lazy/init.lua b/lua/lazy/init.lua index 50aa05f..49a978c 100644 --- a/lua/lazy/init.lua +++ b/lua/lazy/init.lua @@ -2,9 +2,6 @@ local M = {} ---@param opts? LazyConfig function M.setup(opts) - local cache_start = vim.loop.hrtime() - require("lazy.core.cache").setup() - local module_start = vim.loop.hrtime() require("lazy.core.module").setup() local Util = require("lazy.core.util") @@ -13,7 +10,6 @@ function M.setup(opts) local Handler = require("lazy.core.handler") local Plugin = require("lazy.core.plugin") - Util.track("cache", module_start - cache_start) Util.track("module", vim.loop.hrtime() - module_start) Util.track("setup") @@ -29,10 +25,7 @@ function M.setup(opts) for _, plugin in pairs(Config.plugins) do if not plugin._.installed then vim.cmd("do User LazyInstallPre") - require("lazy.manage").install({ - wait = true, - show = Config.options.interactive, - }) + require("lazy.manage").install({ wait = true, show = Config.options.interactive }) break end end @@ -43,9 +36,10 @@ function M.setup(opts) Handler.setup() Util.track() - local lazy_delta = vim.loop.hrtime() - cache_start + local lazy_delta = vim.loop.hrtime() - module_start Util.track() -- end setup + Loader.init_plugins() if Config.plugins["lazy.nvim"] then