Skip to content

Commit

Permalink
util (ble/util/idle): process events before idle sleep
Browse files Browse the repository at this point in the history
  • Loading branch information
akinomyoga committed Mar 1, 2023
1 parent cc852dc commit 559d64b
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/ChangeLog.md
Expand Up @@ -230,6 +230,7 @@
- decode (`vi_imap-rlfunc.txt`): update the widget for `backward-kill-word` as `kill-backward-{u => c}word` `#D1896` e19b796
- term (`_ble_term_TERM`): detect wezterm-20220408 `#D1909` 486564a
- color: rearrange color table by `ble palette` (suggested by stackoverflow/caoanan) `#D1961` bb8541d
- util (`ble/util/idle`): process events before idle sleep `#D1980` xxxxxxx

## Fixes

Expand Down
178 changes: 178 additions & 0 deletions note.txt
Expand Up @@ -6590,6 +6590,184 @@ bash_tips

2023-02-27

* idle: prompt-defer による自動更新が発生していない気がする [#D1980]

以前は background で処理が完了し次第プロンプトが更新される様にしてその様に
実際に動いていた気がする。しかし現在はキー入力が来るまで更新されない様だ。

→実は idle sleep の最中には表示更新が発生しないという可能性? 或る一定以上
の時間 sleep するのであれば更新するべきである。

* reject: sleep の interval を決定する際に使っている時間が idle.do を開始し
てからの時間になっているがそれで良いのだろうか。最後の task からの時間の
方が良いのではないか。

と思ったが、screen saver 的な使い方や定期的に時刻を更新する使い方を考える
と、task が走る度にリセットされると本来の意図と異なる事になる。なので、や
はり idle.do の開始時刻からの時間を使って決定するという事で良い。

* 取り敢えず待機時間の最初に DO_EVENTS を実行して、それ経由で
ble/application/render を呼び出す様にして見ようと思ったが、これだと毎秒プ
ロンプトの再描画を試行してしまって重いのではないか?

以前に時計を表示する実験をした時にはどのようにしていたか。.blerc にある時
刻表示を確認してみたら時刻を info に表示しているので info だけの更新をそ
の場で呼び出していた。そもそもこれは実際に更新が起こる時に呼び出しを行っ
ているので、再描画を呼び出すのは当たり前である。一方で今回の場合には恐ら
く更新がないのに全体再描画を試行する事になっていて問題である。

DO_EVENTS の中で render を呼び出す条件を指定するべきなのではないか。プロ
ンプト更新の可能性がある場合にそれを伝える仕組みが必要? 現在は一応その枠
組として prompt hash を使っているが、それでも複数のプロンプトを管理してい
ると全てのプロンプト hash を eval しなければならず、それはコスト的に高い
様な気がする。

? また、DO_EVENTS 経由で描画を実行するのも変な気がする。IS_IDLE と同じ箇所
で上書きするのであれば良いかと思っていがが確認してみたところ IS_IDLE は
decode.sh で上書きしている。ble/application/render を入れるとしたら寧ろ
canvas.sh か edit.sh の様な気がするので、別の箇所で定義しなければならない。
それは避けたいので、ここはそれ専用の hook を用意する方が自然なのではない
か。

* reject: ble/textarea#render-defer.idle という枠組みが edit.sh で既に実装
されている事に気づいた。つまり元から遅延して再描画する仕組みが整っていた?
と思ったがこれはどうやらユーザー入力に対して反応しているのに過ぎない様だ。
更に使っている箇所を確認するとこれは prompt-defer ではなくて単語着色に時
間がかかっている時に先にユーザー入力を処理する、という時に使っている。
wait-for-user-input で中断するかどうかを確認しているのはこれが理由という
事だろう。なのでこれは今回のものとは関係ないし、今回使えるものでもない気
がする。

プロンプト更新の必要性がある可能性があるという事を示す枠組みを入れる? 現在
の invalidate 関係の仕組みがどうなっているのかについて確認する必要がある。

a 先ず以下の panel::invalidate によって 設定される変数に関しては、そのパネ
ル全体について実際に再描画のシーケンスを出力する必要がある事を示している。
つまり、「内容が更新されている可能性がある&もしチェックして必要があれば変
わった部分だけ更新する」というものに使われるものではない。

_ble_prompt_status_dirty=1
_ble_edit_info_invalidated=1
_ble_textarea_invalidated=1

b 次に _ble_prompt_update_dirty は単に現在既にプロンプトが表示されているか
どうかの判定に使っている様な気がする。

% と思ったが処理の流れが何か変な気がする。dirty だったら dirty=dirty という
% 値を設定する。その次の呼び出しで dirty だったら dirty=done に設定し直して
% 改めて再描画するという構造になっている。これは何だかおかしい気がする。必
% ず2回呼び出されるという事?
%
% 実際にこの部分を追加した commit は e199beee であり #D1750 である。この議
% 論では wezterm integration が勝手に PROMPT_COMMAND から何か文字列を出力す
% るという問題に対する workaround を追加している。どうも textarea 以外の枠
% 組みから ble/prompt/update が呼び出された時に、その時点で dirty という事
% になって、それをその後で処理したい時に done を指定している気がする。
%
% うーん。実はプロンプト毎にも _ble_prompt_ps1_dirty 等の様なものを管理して
% いるのでプロンプト自体が毎回重複して出力されるという訳では無い様だ。然し、
% そうだとしても ble/prompt/update が必ず2回 dirty を返すという事になってし
% まって、だとしたらそれはおかしいのではないか?
%
% と思って実際に試してみたが一回しか prompt dirty になっていない。何故だろ
% うか。分かった。そもそもプロンプト内容に変更がなければ dirty にならないの
% である。なので確かめる時には cd の直後の振る舞いを調べるべきである。
%
% うーん。実際に cd 直後のプロンプトの初回更新時に見てみると既に最初の呼び
% 出しの時点でちゃんと _ble_prompt_update_dirty=done になっている。何故?

分かった。現在の実装では ble/application/render の時点で既に
ble/prompt/update を呼び出す事になっているのである。なので、
textarea#render からの ble/prompt/update の呼び出しは単に先に呼び出した時
の計算結果を参照しているのに過ぎない。:check-dirty: というのは実際に
prompt update 操作を試行せずに結果だけ読み取るという取り扱いなのであって、
同時に実際にその結果を使って描画を行うから dity state をクリアするという
役割があるという事なのである。

そしてそもそも :check-dirty: なしでの呼び出しは ble/application/render か
ら必ず毎回行われるので、:check-dirty: 以降のコードも
ble/application/render を呼び出す場合には毎回実行される。なので、
_ble_prompt_update_dirty の値は実際には ble/prompt/update 処理の省略に使
われている訳では無いという事なのである。

c ble/prompt/clear ... これが最も怪しい。と思ったが、この関数を呼び出すと結
局内部で textarea#invalidate を呼び出しているので全体の強制再描画を引き起
こしてしまう。これはではない気がする。

或いは新しい関数を追加してプロンプト情報の更新だけ試みる? hash だけクリア
すれば良いのでは? と思ったが、今は ble/application/render 自体の呼び出し
を省略できないかという事を考えているのであって、hash をクリアして更に
ble/application/render を呼び出すというのでは半分だけである。

というか b の項目は実は余り関係なかったという事が判明した今改めて
_ble_prompt_hash の取り扱いを見てみると、別に _ble_prompt_hash を更新して
いようが更新していまいが ble/prompt/update の各プロンプトの hash 値確認は
全部行っているので、 _ble_prompt_hash をクリアしようがしまいが
ble/application/render を呼び出す限りは同じ事の気がする。

今欲しい物は何かについて改めて考える。idle.do の中で何か更新が必要になって
いないか確認し、もし必要になっていたら強制的に再描画を実施するという事を考
えている。

d 或いは application/render を実際に毎回呼び出してしまっても良いのではない
か? タスクが走る度に実行するとは言っても、実際に本当にタスクを走らせてい
るのであれば、呼び出してしまっても問題がない気がする。

→これで実装してみたら実際に思うように即時に反映される様になったが、今度
は逆に ble/application/render が各キー入力の後に重複して呼び出されるよう
になった。うーん。そもそも ble/application/render を先に呼び出している筈
なので、改めて呼び出す必要はない筈なのである。

今気づいたのだが、実はそもそも auto-complete すら動いていなかった。sleep し
ている時には定期的に抜けて処理を実行する必要があるのであった。

do_events で ble/application/render を呼び出す様にしたがそれでも重複して
ble/application/render が呼び出されている様だ。更に、重複して呼び出されてい
るのにも関わらずプロンプトが期待通りに更新されていない。二つの問題があるの
で分けて考える。

* idle do_events がキー入力毎に複数回呼び出されるのは何故か。

% 背景処理がそんなに実行されるのだろうか。どういう処理が実行されているの
% か出力して観察してみる事にする。
%
% →idle.do の中から呼び出しているのは一回である。これは期待通りである。一
% 方で、bind/.tail からの呼び出しが二回ある。これは期待していない。
%
% 一方で histdb をロードしていない時には各キーストロークに対して2回実行して
% いる。これは期待通りである。idle を実行する前と idle を実行した後。
%
% うーん。これは auto-complete を無効にしても同様である。なので、
% auto-complete を抜ける時に内部的に生成しているキーが原因ではない。という
% かそもそも内部的に生成するキーに関しては bind/.tail は呼び出されない気が
% する。

分かった。これは (1) ユーザー入力があって idle.do から抜ける時 (2) ユーザー
入力の処理の後に抜ける時 (3) 更に auto-complete 等を処理した後に sleep を
開始した時 の三回処理が実行されているという事である。

実際には単に sleep しているだけで最終的に何も実行する前にユーザー入力があっ
た時には抜ける時の render は必要ない。do_events に対する処理は実は
idle.do の内部で呼び出すべきなのかもしれない。そもそも idle.do が中で実際
に処理が行われたかどうかに応じて修了スターテスを決めるというのも変な気が
する。と思ったが「最後にタスクを処理してから do_events を実行したかどうか」
を終了ステータスにするのはもっと変だ。

うーん。do_events は after_task, onaftertask, on_after_task 等に変更する。
blehook idle_on_after_task, blehook idle_after_task うーん。
idle_after_task という名前にする事にする。

実装した。idle 後の再描画も idle_after_task 経由で実行する事にした。取り
敢えず期待通りに動いている。

* プロンプトが prompt-defer で更新されないと思っていたが、上の問題を解決し
たら何故かこちらの問題も発生しなくなっていた。関係していたのだろうか。或
いは、表示されてはいたけれどもデバグ用の情報出力によって紛れて更新されて
いないと勘違いしていた可能性もある。

何度かやってみたが問題は全く発生しなくなっていた。前はほぼ確実に問題が生
じていたのを考えると実際に解決したのだと思って良い気がする。

* menu (linewise): 行番号を表示すると表示レイアウトが壊れる (reported by bkerin) [#D1979]
https://github.com/akinomyoga/ble.sh/discussions/284
https://github.com/akinomyoga/ble.sh/issues/286
Expand Down
1 change: 1 addition & 0 deletions src/def.sh
Expand Up @@ -34,6 +34,7 @@ blehook/declare DETACH

blehook/declare term_DA1R
blehook/declare term_DA2R
blehook/declare idle_after_task

# color.sh

Expand Down
5 changes: 3 additions & 2 deletions src/edit.sh
Expand Up @@ -302,6 +302,7 @@ function ble/application/render {
ble/application/onwinch
fi
}
blehook idle_after_task!=ble/application/render

## @fn ble/application/onwinch/panel.process-redraw-here
## @arr[in] _ble_app_winsize
Expand Down Expand Up @@ -10216,14 +10217,14 @@ function ble-edit/bind/.tail-without-draw {
if ((_ble_bash>=40000)); then
function ble-edit/bind/.tail {
ble/application/render
ble/util/idle.do && ble/application/render
ble/util/idle.do
ble/textarea#adjust-for-bash-bind # bash-4.0+
ble-edit/bind/stdout.off
}
else
function ble-edit/bind/.tail {
ble/application/render
ble/util/idle.do && ble/application/render
ble/util/idle.do
# bash-3 では READLINE_LINE を設定する方法はないので常に 0 幅
ble-edit/bind/stdout.off
}
Expand Down
24 changes: 22 additions & 2 deletions src/util.sh
Expand Up @@ -4829,7 +4829,7 @@ function ble/util/clock/.initialize {
ble/util/clock/.initialize 2>/dev/null

if ((_ble_bash>=40000)); then
## @fn[custom] ble/util/idle/IS_IDLE { ble/util/is-stdin-ready; }
## @fn[custom] ble/util/idle/IS_IDLE
## 他にするべき処理がない時 (アイドル時) に終了ステータス 0 を返します。
## Note: この設定関数は ble-decode.sh で上書きされます。
function ble/util/idle/IS_IDLE { ! ble/util/is-stdin-ready; }
Expand Down Expand Up @@ -4970,6 +4970,7 @@ if ((_ble_bash>=40000)); then
local _idle_is_first=1
local _idle_processed=
local _idle_info_shown=
local _idle_after_task=0
while :; do
local _idle_key
local _idle_next_time= _idle_next_itime= _idle_running= _idle_waiting=
Expand Down Expand Up @@ -4998,6 +4999,8 @@ if ((_ble_bash>=40000)); then
# Note: #D1450 _idle_command が 148 を返したとしても idle.do は中断し
# ない事にした。IS_IDLE と条件が同じとは限らないので。
#((ext==148)) && return 0

((_idle_after_task++))
elif [[ $_idle_status == [FEPC]* ]]; then
_idle_waiting=1
fi
Expand All @@ -5012,8 +5015,18 @@ if ((_ble_bash>=40000)); then

[[ $_idle_info_shown ]] &&
ble/edit/info/immediate-default
ble/util/idle.do/.do-after-task
[[ $_idle_processed ]]
}
## @fn ble/util/idle.do/.do-after-task
## @var[ref] _idle_after_task
function ble/util/idle.do/.do-after-task {
if ((_idle_after_task)); then
# 50ms 以上の待機時間があれば再描画などの処理を試行する。
blehook/invoke idle_after_task
_idle_after_task=0
fi
}
## @fn ble/util/idle.do/.call-task command
## @var[in,out] _idle_next_time
## @var[in,out] _idle_next_itime
Expand Down Expand Up @@ -5086,6 +5099,9 @@ if ((_ble_bash>=40000)); then
[[ $_idle_running ]] && return 0
local isfirst=1
while
# ファイル等他の条件を待っている時は一回だけで外に戻り確認する状態確認
[[ $_idle_waiting && ! $isfirst ]] && break

local sleep_amount=
if [[ $_idle_next_itime ]]; then
local clock=$_ble_util_idle_sclock
Expand All @@ -5101,11 +5117,15 @@ if ((_ble_bash>=40000)); then
sleep_amount=$sleep1
fi
fi
[[ $isfirst && $_idle_waiting ]] || ((sleep_amount>0))
((sleep_amount>0))
do
# Note: 変数 ble_util_idle_elapsed は
# $((bleopt_idle_interval)) の評価時に参照される。
local ble_util_idle_elapsed=$((_ble_util_idle_sclock-_idle_sclock_start))

# sleep_amount が十分に長い場合に idle_after_task が必要あれば実行する
((sleep_amount>50)) && ble/util/idle.do/.do-after-task

local interval=$((bleopt_idle_interval))

if [[ ! $sleep_amount ]] || ((interval<sleep_amount)); then
Expand Down

0 comments on commit 559d64b

Please sign in to comment.