mirror of https://github.com/folke/lazy.nvim.git
perf: tasks are now fully async
This commit is contained in:
parent
36952153ec
commit
0614ca6ca6
|
@ -105,7 +105,7 @@ M.defaults = {
|
||||||
-- leave nil, to automatically select a browser depending on your OS.
|
-- leave nil, to automatically select a browser depending on your OS.
|
||||||
-- If you want to use a specific browser, you can define it here
|
-- If you want to use a specific browser, you can define it here
|
||||||
browser = nil, ---@type string?
|
browser = nil, ---@type string?
|
||||||
throttle = 20, -- how frequently should the ui process render events
|
throttle = 1000 / 30, -- how frequently should the ui process render events
|
||||||
custom_keys = {
|
custom_keys = {
|
||||||
-- You can define custom key maps here. If present, the description will
|
-- You can define custom key maps here. If present, the description will
|
||||||
-- be shown in the help menu.
|
-- be shown in the help menu.
|
||||||
|
|
|
@ -4,7 +4,45 @@ local Process = require("lazy.manage.process")
|
||||||
---@field skip? fun(plugin:LazyPlugin, opts?:TaskOptions):any?
|
---@field skip? fun(plugin:LazyPlugin, opts?:TaskOptions):any?
|
||||||
---@field run fun(task:LazyTask, opts:TaskOptions)
|
---@field run fun(task:LazyTask, opts:TaskOptions)
|
||||||
|
|
||||||
---@alias LazyTaskState fun():boolean?
|
---@alias LazyTaskState {task:LazyTask, thread:thread}
|
||||||
|
|
||||||
|
local Scheduler = {}
|
||||||
|
---@type LazyTaskState[]
|
||||||
|
Scheduler._queue = {}
|
||||||
|
Scheduler._executor = assert(vim.loop.new_check())
|
||||||
|
Scheduler._running = false
|
||||||
|
|
||||||
|
function Scheduler.step()
|
||||||
|
Scheduler._running = true
|
||||||
|
local budget = 1 * 1e6
|
||||||
|
local start = vim.loop.hrtime()
|
||||||
|
local count = #Scheduler._queue
|
||||||
|
local i = 0
|
||||||
|
while #Scheduler._queue > 0 and vim.loop.hrtime() - start < budget do
|
||||||
|
---@type LazyTaskState
|
||||||
|
local state = table.remove(Scheduler._queue, 1)
|
||||||
|
state.task:_step(state.thread)
|
||||||
|
if coroutine.status(state.thread) ~= "dead" then
|
||||||
|
table.insert(Scheduler._queue, state)
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
if i >= count then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Scheduler._running = false
|
||||||
|
if #Scheduler._queue == 0 then
|
||||||
|
return Scheduler._executor:stop()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param state LazyTaskState
|
||||||
|
function Scheduler.add(state)
|
||||||
|
table.insert(Scheduler._queue, state)
|
||||||
|
if not Scheduler._executor:is_active() then
|
||||||
|
Scheduler._executor:start(vim.schedule_wrap(Scheduler.step))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@class LazyTask
|
---@class LazyTask
|
||||||
---@field plugin LazyPlugin
|
---@field plugin LazyPlugin
|
||||||
|
@ -13,11 +51,11 @@ local Process = require("lazy.manage.process")
|
||||||
---@field status string
|
---@field status string
|
||||||
---@field error? string
|
---@field error? string
|
||||||
---@field warn? string
|
---@field warn? string
|
||||||
---@field private _task fun(task:LazyTask)
|
---@field private _task fun(task:LazyTask, opts:TaskOptions)
|
||||||
---@field private _running LazyPluginState[]
|
|
||||||
---@field private _started? number
|
---@field private _started? number
|
||||||
---@field private _ended? number
|
---@field private _ended? number
|
||||||
---@field private _opts TaskOptions
|
---@field private _opts TaskOptions
|
||||||
|
---@field private _threads thread[]
|
||||||
local Task = {}
|
local Task = {}
|
||||||
|
|
||||||
---@class TaskOptions: {[string]:any}
|
---@class TaskOptions: {[string]:any}
|
||||||
|
@ -32,18 +70,17 @@ function Task.new(plugin, name, task, opts)
|
||||||
__index = Task,
|
__index = Task,
|
||||||
})
|
})
|
||||||
self._opts = opts or {}
|
self._opts = opts or {}
|
||||||
self._running = {}
|
self._threads = {}
|
||||||
self._task = task
|
self._task = task
|
||||||
self._started = nil
|
self._started = nil
|
||||||
self.plugin = plugin
|
self.plugin = plugin
|
||||||
self.name = name
|
self.name = name
|
||||||
self.output = ""
|
self.output = ""
|
||||||
self.status = ""
|
self.status = ""
|
||||||
plugin._.tasks = plugin._.tasks or {}
|
|
||||||
---@param other LazyTask
|
---@param other LazyTask
|
||||||
plugin._.tasks = vim.tbl_filter(function(other)
|
plugin._.tasks = vim.tbl_filter(function(other)
|
||||||
return other.name ~= name or other:is_running()
|
return other.name ~= name or other:is_running()
|
||||||
end, plugin._.tasks)
|
end, plugin._.tasks or {})
|
||||||
table.insert(plugin._.tasks, self)
|
table.insert(plugin._.tasks, self)
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
@ -52,27 +89,26 @@ function Task:has_started()
|
||||||
return self._started ~= nil
|
return self._started ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Task:has_ended()
|
||||||
|
return self._ended ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
function Task:is_done()
|
function Task:is_done()
|
||||||
return self:has_started() and not self:is_running()
|
return self:has_started() and self:has_ended()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Task:is_running()
|
function Task:is_running()
|
||||||
return self:has_started() and self._ended == nil
|
return self:has_started() and not self:has_ended()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Task:start()
|
function Task:start()
|
||||||
if vim.in_fast_event() then
|
assert(not self:has_started(), "task already started")
|
||||||
return vim.schedule(function()
|
assert(not self:has_ended(), "task already done")
|
||||||
self:start()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
self._started = vim.uv.hrtime()
|
self._started = vim.uv.hrtime()
|
||||||
---@type boolean, string|any
|
self:async(function()
|
||||||
local ok, err = pcall(self._task, self, self._opts)
|
self._task(self, self._opts)
|
||||||
if not ok then
|
end)
|
||||||
self.error = err or "failed"
|
|
||||||
end
|
|
||||||
self:_check()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param msg string|string[]
|
---@param msg string|string[]
|
||||||
|
@ -102,36 +138,33 @@ end
|
||||||
---@param fn async fun()
|
---@param fn async fun()
|
||||||
function Task:async(fn)
|
function Task:async(fn)
|
||||||
local co = coroutine.create(fn)
|
local co = coroutine.create(fn)
|
||||||
local check = vim.uv.new_check()
|
table.insert(self._threads, co)
|
||||||
check:start(vim.schedule_wrap(function()
|
Scheduler.add({ task = self, thread = co })
|
||||||
local status = coroutine.status(co)
|
|
||||||
if status == "dead" then
|
|
||||||
check:stop()
|
|
||||||
self:_check()
|
|
||||||
elseif status == "suspended" then
|
|
||||||
local ok, res = coroutine.resume(co)
|
|
||||||
if not ok then
|
|
||||||
error(res)
|
|
||||||
elseif res then
|
|
||||||
self.status = res
|
|
||||||
self.output = self.output .. "\n" .. res
|
|
||||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end))
|
|
||||||
|
|
||||||
table.insert(self._running, function()
|
|
||||||
return check:is_active()
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@param co thread
|
||||||
function Task:_check()
|
function Task:_step(co)
|
||||||
for _, state in ipairs(self._running) do
|
local status = coroutine.status(co)
|
||||||
if state() then
|
if status == "suspended" then
|
||||||
|
local ok, res = coroutine.resume(co)
|
||||||
|
if not ok then
|
||||||
|
self:notify_error(tostring(res))
|
||||||
|
elseif res then
|
||||||
|
self:notify(tostring(res))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, t in ipairs(self._threads) do
|
||||||
|
if coroutine.status(t) ~= "dead" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
self:_done()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
function Task:_done()
|
||||||
|
assert(self:has_started(), "task not started")
|
||||||
|
assert(not self:has_ended(), "task already done")
|
||||||
self._ended = vim.uv.hrtime()
|
self._ended = vim.uv.hrtime()
|
||||||
if self._opts.on_done then
|
if self._opts.on_done then
|
||||||
self._opts.on_done(self)
|
self._opts.on_done(self)
|
||||||
|
@ -147,29 +180,13 @@ function Task:time()
|
||||||
if not self:has_started() then
|
if not self:has_started() then
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
if not self:is_done() then
|
if not self:has_ended() then
|
||||||
return (vim.uv.hrtime() - self._started) / 1e6
|
return (vim.uv.hrtime() - self._started) / 1e6
|
||||||
end
|
end
|
||||||
return (self._ended - self._started) / 1e6
|
return (self._ended - self._started) / 1e6
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param fn fun()
|
---@async
|
||||||
function Task:schedule(fn)
|
|
||||||
local done = false
|
|
||||||
table.insert(self._running, function()
|
|
||||||
return not done
|
|
||||||
end)
|
|
||||||
vim.schedule(function()
|
|
||||||
---@type boolean, string|any
|
|
||||||
local ok, err = pcall(fn)
|
|
||||||
if not ok then
|
|
||||||
self.error = err or "failed"
|
|
||||||
end
|
|
||||||
done = true
|
|
||||||
self:_check()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param cmd string
|
---@param cmd string
|
||||||
---@param opts? ProcessOpts
|
---@param opts? ProcessOpts
|
||||||
function Task:spawn(cmd, opts)
|
function Task:spawn(cmd, opts)
|
||||||
|
@ -178,6 +195,7 @@ function Task:spawn(cmd, opts)
|
||||||
local on_exit = opts.on_exit
|
local on_exit = opts.on_exit
|
||||||
|
|
||||||
function opts.on_line(line)
|
function opts.on_line(line)
|
||||||
|
self:notify(line)
|
||||||
self.status = line
|
self.status = line
|
||||||
if on_line then
|
if on_line then
|
||||||
pcall(on_line, line)
|
pcall(on_line, line)
|
||||||
|
@ -185,6 +203,7 @@ function Task:spawn(cmd, opts)
|
||||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local running = true
|
||||||
---@param output string
|
---@param output string
|
||||||
function opts.on_exit(ok, output)
|
function opts.on_exit(ok, output)
|
||||||
self.output = self.output .. output
|
self.output = self.output .. output
|
||||||
|
@ -194,12 +213,12 @@ function Task:spawn(cmd, opts)
|
||||||
if on_exit then
|
if on_exit then
|
||||||
pcall(on_exit, ok, output)
|
pcall(on_exit, ok, output)
|
||||||
end
|
end
|
||||||
self:_check()
|
running = false
|
||||||
|
end
|
||||||
|
Process.spawn(cmd, opts)
|
||||||
|
while running do
|
||||||
|
coroutine.yield()
|
||||||
end
|
end
|
||||||
local proc = Process.spawn(cmd, opts)
|
|
||||||
table.insert(self._running, function()
|
|
||||||
return proc and not proc:is_closing()
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param tasks (LazyTask?)[]
|
---@param tasks (LazyTask?)[]
|
||||||
|
|
Loading…
Reference in New Issue