🕰️

Vim scriptで関数のdebounceとthrottle

2024/03/22に公開

debounce / throttleとは、一定間隔で処理を間引く機能(手法)のことです。
今回はこれをVim scriptで作ってみました。

lambdalisue/gin.vimのこちらを参考にしました。

https://github.com/lambdalisue/gin.vim/blob/937cc4dd3b5b1fbc90a21a8b8318b1c9d2d7c2cd/autoload/gin/internal/util.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>

上記のスクリプトを読み込んだ状態でカーソルを移動させると、以下のように移動するたびに行番号が表示されます(たまにエコーが消えていますが気にしないでください)。

https://youtu.be/KZOhkARm_rM

debounceを挟んでみましょう。ここでは待機時間は500ミリ秒としました。

nnoremap j j<cmd>call Debounce('Echoline', 500)<cr>
nnoremap k k<cmd>call Debounce('Echoline', 500)<cr>

連続して移動している間は何も表示されず、移動が終了して500ミリ秒後に初めて表示されます。

https://youtu.be/3zZH0w6K07c

続いてthrottleの例です。

nnoremap j j<cmd>call Throttle('Echoline', 500)<cr>
nnoremap k k<cmd>call Throttle('Echoline', 500)<cr>

こちらは移動に伴って行番号が表示されます。ただし、一度表示されると、そこから500ミリ秒間は再表示がされません。

https://youtu.be/wDdoq7C8eoI

おわりに

重たい計算をする場合や外部と通信する場合など、あまり高頻度に実行されたくない処理を使う場合に有用です。
debounceとthrottleでは実行されるタイミングが異なるので、状況にあわせて選択してください。

Discussion