Improve performance by caching subtree state and configure a single root for simplicity

This commit is contained in:
phosphene47
2021-11-14 22:01:21 +11:00
parent 3d6e92b587
commit 2b446d9d43
2 changed files with 114 additions and 66 deletions

View File

@ -4,7 +4,7 @@ local proc = require("dep/proc")
logger:open() logger:open()
local initialized, config_path, base_dir local initialized, config_path, base_dir
local packages, package_roots local packages, root
local function get_name(id) local function get_name(id)
local name = id:match("^[%w-_.]+/([%w-_.]+)$") local name = id:match("^[%w-_.]+/([%w-_.]+)$")
@ -22,7 +22,7 @@ local function link_dependency(parent, child)
end end
if not child.dependencies[parent.id] then if not child.dependencies[parent.id] then
child.dependencies[parent.id], child.root = parent, false child.dependencies[parent.id] = parent
child.dependencies[#child.dependencies + 1] = parent child.dependencies[#child.dependencies + 1] = parent
end end
end end
@ -45,10 +45,11 @@ local function register(spec, overrides)
added = false, added = false,
configured = false, configured = false,
loaded = false, loaded = false,
subtree_configured = false,
subtree_loaded = false,
on_setup = {}, on_setup = {},
on_config = {}, on_config = {},
on_load = {}, on_load = {},
root = true,
dependencies = {}, -- inward edges dependencies = {}, -- inward edges
dependents = {}, -- outward edges dependents = {}, -- outward edges
} }
@ -76,6 +77,11 @@ local function register(spec, overrides)
package.on_config[#package.on_config + 1] = spec.config package.on_config[#package.on_config + 1] = spec.config
package.on_load[#package.on_load + 1] = spec[2] package.on_load[#package.on_load + 1] = spec[2]
-- every package is implicitly dependent on us, the package manager
if root and package ~= root then
link_dependency(root, package)
end
if type(spec.requires) == "table" then if type(spec.requires) == "table" then
for i = 1, #spec.requires do for i = 1, #spec.requires do
link_dependency(register(spec.requires[i]), package) link_dependency(register(spec.requires[i]), package)
@ -128,10 +134,10 @@ local function register_recursive(list, overrides)
end end
local function sort_dependencies() local function sort_dependencies()
-- we don't do topological sort, packages are loaded by traversing the graph recursively
-- any sorting is fine as long as the order is consistent and predictable
local function compare(a, b) local function compare(a, b)
local a_deps = #a.dependencies local a_deps, b_deps = #a.dependencies, #b.dependencies
local b_deps = #b.dependencies
if a_deps == b_deps then if a_deps == b_deps then
return a.id < b.id return a.id < b.id
else else
@ -216,15 +222,6 @@ local function ensure_acyclic()
end end
end end
local function find_roots()
for i = 1, #packages do
local package = packages[i]
if package.root then
package_roots[#package_roots + 1] = package
end
end
end
local function run_hooks(package, type) local function run_hooks(package, type)
local hooks = package[type] local hooks = package[type]
@ -239,7 +236,7 @@ local function run_hooks(package, type)
if #hooks ~= 0 then if #hooks ~= 0 then
logger:log( logger:log(
"hook", "hook",
string.format("ran %d %s %s for %s", #hooks, type, #hooks == 1 and "hook" or "hooks", package.id) string.format("triggered %d %s %s for %s", #hooks, type, #hooks == 1 and "hook" or "hooks", package.id)
) )
end end
@ -262,7 +259,7 @@ local function ensure_added(package)
end end
local function configure_recursive(package) local function configure_recursive(package)
if not package.exists or not package.enabled or package.error then if not package.exists or not package.enabled or package.error or package.subtree_configured then
return return
end end
@ -272,8 +269,6 @@ local function configure_recursive(package)
end end
end end
local propagate = false
if not package.configured then if not package.configured then
local ok, err = run_hooks(package, "on_setup") local ok, err = run_hooks(package, "on_setup")
if not ok then if not ok then
@ -293,22 +288,21 @@ local function configure_recursive(package)
return return
end end
package.configured, package.loaded = true, false package.configured = true
propagate = true
logger:log("config", string.format("configured %s", package.id)) logger:log("config", string.format("configured %s", package.id))
end end
for i = 1, #package.dependents do package.subtree_configured = true
local dependent = package.dependents[i]
dependent.configured = dependent.configured and not propagate for i = 1, #package.dependents do
configure_recursive(dependent) package.subtree_configured = configure_recursive(package.dependents[i]) and package.subtree_configured
end end
return package.subtree_configured
end end
local function load_recursive(package) local function load_recursive(package)
if not package.exists or not package.enabled or package.error then if not package.exists or not package.enabled or package.error or package.subtree_loaded then
return return
end end
@ -318,8 +312,6 @@ local function load_recursive(package)
end end
end end
local propagate = false
if not package.loaded then if not package.loaded then
local ok, err = ensure_added(package) local ok, err = ensure_added(package)
if not ok then if not ok then
@ -334,17 +326,16 @@ local function load_recursive(package)
end end
package.loaded = true package.loaded = true
propagate = true
logger:log("load", string.format("loaded %s", package.id)) logger:log("load", string.format("loaded %s", package.id))
end end
for i = 1, #package.dependents do package.subtree_loaded = true
local dependent = package.dependents[i]
dependent.loaded = dependent.loaded and not propagate for i = 1, #package.dependents do
load_recursive(dependent, force) package.subtree_loaded = load_recursive(package.dependents[i]) and package.subtree_loaded
end end
return package.subtree_loaded
end end
local function reload_meta() local function reload_meta()
@ -363,21 +354,30 @@ local function reload_meta()
end end
end end
local function reload_all() local function reload()
-- clear all errors and try again -- clear errors to retry
for i = 1, #packages do for i = 1, #packages do
packages[i].error = false packages[i].error = false
end end
for i = 1, #package_roots do local reloaded
configure_recursive(package_roots[i]) reloaded = configure_recursive(root) or reloaded
reloaded = load_recursive(root) or reloaded
if reloaded then
reload_meta()
end end
for i = 1, #package_roots do return reloaded
load_recursive(package_roots[i]) end
local function reload_all()
for i = 1, #packages do
local package = packages[i]
package.loaded, package.subtree_loaded = false, false
end end
reload_meta() reload()
end end
local function clean() local function clean()
@ -416,6 +416,28 @@ local function clean()
) )
end end
local function mark_reconfigure(package)
local function mark_dependencies(node)
node.subtree_configured, node.subtree_loaded = false, false
for i = 1, #node.dependencies do
mark_dependencies(node.dependencies[i])
end
end
local function mark_dependents(node)
node.configured, node.loaded, node.added = false, false, false
node.subtree_configured, node.subtree_loaded = false, false
for i = 1, #node.dependents do
mark_dependents(node.dependents[i])
end
end
mark_dependencies(package)
mark_dependents(package)
end
local function sync(package, cb) local function sync(package, cb)
if not package.enabled then if not package.enabled then
return return
@ -452,7 +474,7 @@ local function sync(package, cb)
if err then if err then
log_err(message) log_err(message)
else else
package.added, package.configured = false, false mark_reconfigure(package)
logger:log("update", string.format("updated %s; %s -> %s", package.id, before, after)) logger:log("update", string.format("updated %s; %s -> %s", package.id, before, after))
end end
@ -469,7 +491,8 @@ local function sync(package, cb)
if err then if err then
logger:log("error", string.format("failed to install %s; reason: %s", package.id, message)) logger:log("error", string.format("failed to install %s; reason: %s", package.id, message))
else else
package.exists, package.added, package.configured = true, false, false package.exists = true
mark_reconfigure(package)
logger:log("install", string.format("installed %s", package.id)) logger:log("install", string.format("installed %s", package.id))
end end
@ -488,10 +511,12 @@ local function sync_list(list)
if progress == #list then if progress == #list then
clean() clean()
reload_all() reload()
if has_errors then if has_errors then
logger:log("error", "there were errors during sync; see :messages or :DepLog for more information") logger:log("error", "there were errors during sync; see :messages or :DepLog for more information")
else
logger:log("update", string.format("synchronized %s %s", #list, #list == 1 and "package" or "packages"))
end end
end end
end end
@ -501,7 +526,7 @@ local function sync_list(list)
end end
end end
local function print_list(list) local function print_list()
local buffer = vim.api.nvim_create_buf(true, true) local buffer = vim.api.nvim_create_buf(true, true)
local line = 0 local line = 0
local indent = 0 local indent = 0
@ -539,19 +564,26 @@ local function print_list(list)
line = line + 1 line = line + 1
end end
print("Installed packages:") print({
indent = 1 { "Installed packages:" },
{ string.format(" (%s)", #packages), "Comment" },
})
indent = 1
local loaded = {} local loaded = {}
local function dry_load(package) local function dry_load(package)
if loaded[package.id] then
return
end
for i = 1, #package.dependencies do for i = 1, #package.dependencies do
if not loaded[package.dependencies[i].id] then if not loaded[package.dependencies[i].id] then
return return
end end
end end
loaded[package.id] = true loaded[package.id], loaded[#loaded + 1] = true, package
local line = { local line = {
{ "- ", "Comment" }, { "- ", "Comment" },
@ -581,21 +613,33 @@ local function print_list(list)
end end
end end
for i = 1, #package_roots do dry_load(root)
dry_load(package_roots[i])
end
indent = 0 indent = 0
print() print()
print("Dependency graph:") print("Dependency graph:")
local function walk_graph(package) local function walk_graph(package)
indent = indent + 1 indent = indent + 1
print({ local line = {
{ "| ", "Comment" }, { "| ", "Comment" },
{ package.id, "Underlined" }, { package.id, "Underlined" },
}) }
local function add_edges(package)
for i = 1, #package.dependencies do
local dependency = package.dependencies[i]
if dependency ~= root then -- don't convolute the list
line[#line + 1] = { " " .. dependency.id, "Comment" }
add_edges(dependency)
end
end
end
add_edges(package)
print(line)
for i = 1, #package.dependents do for i = 1, #package.dependents do
walk_graph(package.dependents[i]) walk_graph(package.dependents[i])
@ -604,9 +648,17 @@ local function print_list(list)
indent = indent - 1 indent = indent - 1
end end
for i = 1, #package_roots do walk_graph(root)
walk_graph(package_roots[i]) indent = 0
print()
print("Debug information:")
local lines = {}
for l in vim.inspect(packages):gmatch("[^\n]+") do
lines[#lines + 1] = l
end end
vim.api.nvim_buf_set_lines(buffer, line, -1, false, lines)
vim.api.nvim_buf_set_name(buffer, "packages.dep") vim.api.nvim_buf_set_name(buffer, "packages.dep")
vim.api.nvim_buf_set_option(buffer, "bufhidden", "wipe") vim.api.nvim_buf_set_option(buffer, "bufhidden", "wipe")
@ -646,10 +698,7 @@ return setmetatable({
reload = wrap_api("dep.reload", reload_all), reload = wrap_api("dep.reload", reload_all),
clean = wrap_api("dep.clean", clean), clean = wrap_api("dep.clean", clean),
list = wrap_api("dep.list", print_list),
list = wrap_api("dep.list", function()
print_list(packages)
end),
open_log = wrap_api("dep.open_log", function() open_log = wrap_api("dep.open_log", function()
vim.cmd("sp " .. logger.path) vim.cmd("sp " .. logger.path)
@ -663,14 +712,13 @@ return setmetatable({
config_path = debug.getinfo(2, "S").source:sub(2) config_path = debug.getinfo(2, "S").source:sub(2)
initialized, err = pcall(function() initialized, err = pcall(function()
base_dir = config.base_dir or (vim.fn.stdpath("data") .. "/site/pack/deps/opt/") base_dir = config.base_dir or (vim.fn.stdpath("data") .. "/site/pack/deps/opt/")
packages, package_roots = {}, {} packages = {}
register("chiyadev/dep") root = register("chiyadev/dep")
register_recursive(config) register_recursive(config)
sort_dependencies() sort_dependencies()
ensure_acyclic() ensure_acyclic()
find_roots() reload()
reload_all()
local should_sync = function(package) local should_sync = function(package)
if config.sync == "new" or config.sync == nil then if config.sync == "new" or config.sync == nil then

View File

@ -4,7 +4,7 @@ local logger = {
} }
local colors = { local colors = {
skip = "Constant", skip = "Comment",
clean = "Boolean", clean = "Boolean",
install = "MoreMsg", install = "MoreMsg",
update = "WarningMsg", update = "WarningMsg",