Vim scriptで関数のdebounceとthrottle
debounce / throttleとは、一定間隔で処理を間引く機能(手法)のことです。
今回はこれをVim scriptで作ってみました。
lambdalisue/gin.vimのこちらを参考にしました。
Debounce
参考元はコマンドをdebounceする機能だったので、関数を対象とする形に改造しました。引数は、関数名または参照(call()
に渡せるもの)、待機時間(ミリ秒)、実行する関数の引数(任意)です。
内部では、指定された時間経過後に関数が実行されるタイマーを設定します。
debounce関数が連続して呼び出されると、タイマーが作り直され、第一引数の関数の実行は先送りされます。
" run last one call in wait time
let s:debounce_timers = {}
function Debounce(fn, wait, args = []) abort
let timer_name = string(a:fn)
call get(s:debounce_timers, timer_name, 0)->timer_stop()
let s:debounce_timers[timer_name] = timer_start(a:wait, {-> call(a:fn, a:args) })
endfunction
Throttle
インターフェースと、内部で第一引数の関数を実行するタイマーを設定する点はdebounceと共通です。
こちらでは、関数実行用タイマーが未完了の場合、タイマー再定義をスキップします。
" run first one call in wait time
let s:throttle_timers = {}
function Throttle(fn, wait, args = []) abort
let timer_name = string(a:fn)
if get(s:throttle_timers, timer_name, 0)
return
endif
let s:throttle_timers[timer_name] = timer_start(a:wait, {->[
\ call(a:fn, a:args),
\ execute('unlet! s:throttle_timers[timer_name]')
\ ]})
endfunction
Examples
通常実行、debounce、throttleの動作の差を示す例を作りました。
現在の行番号を表示するEcholine()
という関数をつくり、それをj
/ k
の移動後に呼び出します。
function! Echoline() abort
echo line('.')
endfunction
nnoremap j j<cmd>call Echoline()<cr>
nnoremap k k<cmd>call Echoline()<cr>
上記のスクリプトを読み込んだ状態でカーソルを移動させると、以下のように移動するたびに行番号が表示されます(たまにエコーが消えていますが気にしないでください)。
debounceを挟んでみましょう。ここでは待機時間は500ミリ秒としました。
nnoremap j j<cmd>call Debounce('Echoline', 500)<cr>
nnoremap k k<cmd>call Debounce('Echoline', 500)<cr>
連続して移動している間は何も表示されず、移動が終了して500ミリ秒後に初めて表示されます。
続いてthrottleの例です。
nnoremap j j<cmd>call Throttle('Echoline', 500)<cr>
nnoremap k k<cmd>call Throttle('Echoline', 500)<cr>
こちらは移動に伴って行番号が表示されます。ただし、一度表示されると、そこから500ミリ秒間は再表示がされません。
おわりに
重たい計算をする場合や外部と通信する場合など、あまり高頻度に実行されたくない処理を使う場合に有用です。
debounceとthrottleでは実行されるタイミングが異なるので、状況にあわせて選択してください。
Discussion