refactor(util): reuse timer handles in Debouncer
This commit is contained in:
+3
-12
@@ -1,4 +1,3 @@
|
|||||||
local log = require("log")
|
|
||||||
local util = require("util")
|
local util = require("util")
|
||||||
|
|
||||||
local HIGHLIGHTS = {
|
local HIGHLIGHTS = {
|
||||||
@@ -97,21 +96,13 @@ local Repo = {}
|
|||||||
Repo.__index = Repo
|
Repo.__index = Repo
|
||||||
|
|
||||||
function Repo:start_watcher()
|
function Repo:start_watcher()
|
||||||
local watcher, err_msg, err_name = vim.uv.new_fs_event()
|
local watcher = assert(vim.uv.new_fs_event())
|
||||||
if not watcher then
|
assert(watcher:start(self.gitdir, {}, function(err, filename)
|
||||||
log.error(
|
|
||||||
"Failed to create fs event watcher: %s (%s)",
|
|
||||||
err_msg,
|
|
||||||
err_name
|
|
||||||
)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
watcher:start(self.gitdir, {}, function(err, filename)
|
|
||||||
if err or (filename ~= "index" and filename ~= "HEAD") then
|
if err or (filename ~= "index" and filename ~= "HEAD") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self.refresh:call()
|
self.refresh:call()
|
||||||
end)
|
end))
|
||||||
self.watcher = watcher
|
self.watcher = watcher
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+3
-13
@@ -187,17 +187,7 @@ function M.watch()
|
|||||||
|
|
||||||
local plugins_dir = vim.fs.joinpath(config_dir, "plugins")
|
local plugins_dir = vim.fs.joinpath(config_dir, "plugins")
|
||||||
|
|
||||||
local err_msg, err_name
|
watcher = assert(vim.uv.new_fs_event())
|
||||||
watcher, err_msg, err_name = vim.uv.new_fs_event()
|
|
||||||
if not watcher then
|
|
||||||
log.error(
|
|
||||||
"Failed to create fs event watcher: %s (%s)",
|
|
||||||
err_msg,
|
|
||||||
err_name
|
|
||||||
)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
reload = util.debounce(function(filename)
|
reload = util.debounce(function(filename)
|
||||||
local path = vim.fs.joinpath(plugins_dir, filename)
|
local path = vim.fs.joinpath(plugins_dir, filename)
|
||||||
if not vim.uv.fs_stat(path) then
|
if not vim.uv.fs_stat(path) then
|
||||||
@@ -211,7 +201,7 @@ function M.watch()
|
|||||||
end
|
end
|
||||||
end, 100)
|
end, 100)
|
||||||
|
|
||||||
watcher:start(plugins_dir, {}, function(err, filename)
|
assert(watcher:start(plugins_dir, {}, function(err, filename)
|
||||||
if err then
|
if err then
|
||||||
log.error("Watch error: %s", err)
|
log.error("Watch error: %s", err)
|
||||||
return
|
return
|
||||||
@@ -220,7 +210,7 @@ function M.watch()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
reload:call(filename)
|
reload:call(filename)
|
||||||
end)
|
end))
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.unwatch()
|
function M.unwatch()
|
||||||
|
|||||||
+56
-19
@@ -295,10 +295,16 @@ end
|
|||||||
|
|
||||||
local NIL_KEY = {}
|
local NIL_KEY = {}
|
||||||
|
|
||||||
|
---@class ow.Util.Debouncer.Slot
|
||||||
|
---@field timer uv.uv_timer_t
|
||||||
|
---@field cb function
|
||||||
|
---@field id any
|
||||||
|
---@field args table
|
||||||
|
|
||||||
---@class ow.Util.Debouncer
|
---@class ow.Util.Debouncer
|
||||||
---@field private _fn fun(id: any, ...)
|
---@field private _fn fun(id: any, ...)
|
||||||
---@field private _delay integer
|
---@field private _delay integer
|
||||||
---@field private _timers table<any, uv.uv_timer_t>
|
---@field private _slots table<any, ow.Util.Debouncer.Slot>
|
||||||
local Debouncer = {}
|
local Debouncer = {}
|
||||||
Debouncer.__index = Debouncer
|
Debouncer.__index = Debouncer
|
||||||
|
|
||||||
@@ -306,42 +312,73 @@ Debouncer.__index = Debouncer
|
|||||||
---@param delay integer
|
---@param delay integer
|
||||||
---@return ow.Util.Debouncer
|
---@return ow.Util.Debouncer
|
||||||
function Debouncer.new(fn, delay)
|
function Debouncer.new(fn, delay)
|
||||||
return setmetatable({ _fn = fn, _delay = delay, _timers = {} }, Debouncer)
|
return setmetatable({ _fn = fn, _delay = delay, _slots = {} }, Debouncer)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param slot ow.Util.Debouncer.Slot
|
||||||
|
local function dispose(slot)
|
||||||
|
if not slot.timer:is_closing() then
|
||||||
|
slot.timer:stop()
|
||||||
|
slot.timer:close()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param id? any
|
---@param id? any
|
||||||
---@param ... any
|
---@param ... any
|
||||||
function Debouncer:call(id, ...)
|
function Debouncer:call(id, ...)
|
||||||
local key = id == nil and NIL_KEY or id
|
local key = id == nil and NIL_KEY or id
|
||||||
|
local slot = self._slots[key]
|
||||||
local args = vim.F.pack_len(...)
|
local args = vim.F.pack_len(...)
|
||||||
self:cancel(id)
|
if not slot then
|
||||||
self._timers[key] = vim.defer_fn(function()
|
-- cb lives on the slot so restart (the else branch below) can hand
|
||||||
self._timers[key] = nil
|
-- the same closure back to timer:start, avoiding a fresh
|
||||||
self._fn(id, vim.F.unpack_len(args))
|
-- schedule_wrap allocation per call. It reads id/args from the
|
||||||
end, self._delay)
|
-- slot (not upvalues) so restarts pick up the latest args.
|
||||||
|
local timer = assert(vim.uv.new_timer())
|
||||||
|
---@type ow.Util.Debouncer.Slot
|
||||||
|
local new_slot
|
||||||
|
new_slot = {
|
||||||
|
timer = timer,
|
||||||
|
id = id,
|
||||||
|
args = args,
|
||||||
|
cb = vim.schedule_wrap(function()
|
||||||
|
-- Slot may have been cancelled between libuv firing and
|
||||||
|
-- this scheduled callback running. Identity-check before
|
||||||
|
-- firing.
|
||||||
|
if self._slots[key] ~= new_slot then
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
self._slots[key] = nil
|
||||||
---@param timer uv.uv_timer_t
|
dispose(new_slot)
|
||||||
local function dispose(timer)
|
self._fn(new_slot.id, vim.F.unpack_len(new_slot.args))
|
||||||
timer:stop()
|
end),
|
||||||
timer:close()
|
}
|
||||||
|
self._slots[key] = new_slot
|
||||||
|
slot = new_slot
|
||||||
|
else
|
||||||
|
slot.id = id
|
||||||
|
slot.args = args
|
||||||
|
end
|
||||||
|
-- uv_timer_start on an already-active timer restarts it with the new
|
||||||
|
-- timeout, reusing the same handle (no per-call allocation).
|
||||||
|
assert(slot.timer:start(self._delay, 0, slot.cb))
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param id? any
|
---@param id? any
|
||||||
function Debouncer:cancel(id)
|
function Debouncer:cancel(id)
|
||||||
local key = id == nil and NIL_KEY or id
|
local key = id == nil and NIL_KEY or id
|
||||||
local timer = self._timers[key]
|
local slot = self._slots[key]
|
||||||
if timer then
|
if slot then
|
||||||
dispose(timer)
|
self._slots[key] = nil
|
||||||
self._timers[key] = nil
|
dispose(slot)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Debouncer:cancel_all()
|
function Debouncer:cancel_all()
|
||||||
for _, t in pairs(self._timers) do
|
for _, slot in pairs(self._slots) do
|
||||||
dispose(t)
|
dispose(slot)
|
||||||
end
|
end
|
||||||
self._timers = {}
|
self._slots = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
Util.Debouncer = Debouncer
|
Util.Debouncer = Debouncer
|
||||||
|
|||||||
Reference in New Issue
Block a user