Skip to content

Commit 5dd3832

Browse files
committed
Batch updates
While the performance of tpipeline itself has been optimized quite a while for now, it is still possible for a bottleneck to appear, if the downstream consumer is too slow. The problem does not really show up when using tmux, but when using other external integrations (e.g. kitty), it is possible that the external statusline update can become a bottleneck. Kitty's custom tabbar integration being written in Python does not really help with this. It should be noted that this has never caused any delays in vim itself, given that this bottleneck only happens in the part, that is already running completely asynchronous from vim itself. But it could cause some hilarious problems, where the external statusline starts to lag behind noticably behind the live statusline in vim. To fix this we batch updates by depleting stdin completely before dispatching the external statusline update. Unfortunately there is not really a very good way to test if stdin contains more data just with shell code. We opt to use a hack with "read -t", which will fail immediately if stdin is empty: -t timeout time out and return failure if a complete line of input is not read within TIMEOUT seconds. The value of the TMOUT variable is the default timeout. TIMEOUT may be a fractional number. If TIMEOUT is 0, read returns immediately, without trying to read any data, returning success only if input is available on the specified file descriptor. The exit status is greater than 128 if the timeout is exceeded This is a complete hack, because "-t" is not really portable across sh implementations, e.g. the slightly widely used dash does not support it. We are very lucky with this specific codepath though, because "sh does not support -t" and "there is no more stdin data ready" have the same exit code, thus sh implementations not supporting this flag will automatically fallback to the old unbatched update path, i.e. they will never enter the inner while loop. If this however still ever causes problems in the future, we could think about using bash instead of sh. Both are basically preinstalled everywhere anyway. With this we can also lower the catchup time in the performance test, as the lagging behind is completely fixed now. In any case this also adds a completely new unit test that tests the lag-behind after updating the statusline every millisecond for 3 seconds. Fixes #64
1 parent c1b25c1 commit 5dd3832

File tree

2 files changed

+30
-10
lines changed

2 files changed

+30
-10
lines changed

autoload/tpipeline.vim

+2-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ func tpipeline#fork_job()
174174
let s:restore_right = systemlist("sh -c 'echo \"\"; tmux display-message -p \"#{status-right}\"'")[-1]
175175
endif
176176
let script = printf("export IFS='$\\n'; while read -r l; do%s", g:tpipeline_split ? " read -r r;" : "")
177-
let script .= printf(" echo \"$l\" > '%s'%s", s:tpipeline_filepath, g:tpipeline_split ? printf("; echo \"$r\" > '%s'", s:tpipeline_right_filepath) : "")
177+
let script .= printf(" while read -t 0 _; do read -r l%s; done", g:tpipeline_split ? "; read -r r" : "") " batch updates
178+
let script .= printf("; echo \"$l\" > '%s'%s", s:tpipeline_filepath, g:tpipeline_split ? printf("; echo \"$r\" > '%s'", s:tpipeline_right_filepath) : "")
178179
if g:tpipeline_usepane
179180
" end early if file was truncated so as not to overwrite any titles of panes we may switch to
180181
let script .= "; if [ -z \"$l\" ]; then continue; fi"

tests/test_general.vim

+28-9
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ func Read_socket()
2626
endif
2727
endfunc
2828

29+
func Scroll()
30+
if line(".") - 1
31+
norm k
32+
else
33+
norm G
34+
endif
35+
call tpipeline#update()
36+
endfunc
37+
2938
func Test_loaded()
3039
call assert_equal(1, g:loaded_tpipeline)
3140
endfunc
@@ -143,21 +152,13 @@ func Test_performance()
143152
exec printf("profile start %s", log_file)
144153
profile func tpipeline#update
145154
" simulate someone scrolling at 120FPS
146-
func Scroll()
147-
if line(".") - 1
148-
norm k
149-
else
150-
norm G
151-
endif
152-
call tpipeline#update()
153-
endfunc
154155
let timer = timer_start(float2nr(individual_threshold * 1000), {-> Scroll()}, {'repeat': -1})
155156
exec "sleep " . test_duration
156157

157158
profile stop
158159
call timer_stop(timer)
159160
" wait for tmux to catch up
160-
sleep 2
161+
sleep 200m
161162

162163
let log = readfile(log_file, '', 5)
163164
call assert_equal('FUNCTION tpipeline#update()', log[0])
@@ -220,3 +221,21 @@ func Test_minwid_padded()
220221
call assert_match("a$", s:left)
221222
call assert_true(empty(s:right))
222223
endfunc
224+
225+
func Test_lag_behind()
226+
" statusline should not lag behind even after rapid fire updates
227+
let g:tpipeline_statusline = "%!tpipeline#stl#line()"
228+
let test_duration = "3"
229+
norm 99o
230+
" update literally every single ms
231+
let timer = timer_start(1, {-> Scroll()}, {'repeat': -1})
232+
exec "sleep " . test_duration
233+
call timer_stop(timer)
234+
235+
" now set a new statusline, the new value should appear immediately without any lag
236+
let g:tpipeline_statusline = "RAPIDFIRE"
237+
call Read_socket()
238+
call assert_equal('RAPIDFIRE', Strip_hl(s:left))
239+
240+
bd!
241+
endfunc

0 commit comments

Comments
 (0)