Make the settings screen redraw 50x faster
Arrow-key navigation took 435 ms per keypress, which reads as the whole console reloading on every press - because it effectively was. The cost was forks: each frame ran a command substitution per label for the value, the translation and the rendered state, 54 subshells for eighteen rows. The frame is now assembled in memory and written once. Translations, the split specs and the terminfo clear string are resolved before the loop; values are re-read only after something actually changes, not on cursor movement. Helpers on that path assign to a variable instead of printing, since printing is what forced the substitution. Measured on the same machine: 435 ms -> 7.5 ms per keypress.
This commit is contained in:
1 parent
c308b7e286
commit
774217ed95
4 files changed
+111
-43
No files matched your search
@@ -8,7 +8,7 @@
|
|||||||
# Overridable so a packager can pass the version it is actually building
|
# Overridable so a packager can pass the version it is actually building
|
||||||
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
|
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
|
||||||
# straight from a checkout, and is what a release tag has to carry.
|
# straight from a checkout, and is what a release tag has to carry.
|
||||||
VERSION ?= 1.1.0
|
VERSION ?= 1.1.1
|
||||||
|
|
||||||
PREFIX ?= /usr
|
PREFIX ?= /usr
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
|
|||||||
@@ -75,6 +75,27 @@ cau_msg() {
|
|||||||
cau_msg_in "$(cau_ui_locale)" "$@"
|
cau_msg_in "$(cau_ui_locale)" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# cau_msg_into <locale> <msgid>
|
||||||
|
# Plain lookup with the result in CAU_MSG_RESULT and no printf formatting.
|
||||||
|
# For callers that redraw many labels per keypress, where wrapping cau_msg in a
|
||||||
|
# command substitution would cost a fork per label.
|
||||||
|
CAU_MSG_RESULT=''
|
||||||
|
|
||||||
|
cau_msg_into() {
|
||||||
|
local locale="$1" msgid="$2" cachekey
|
||||||
|
cachekey="${locale}"$'\x1f'"${msgid}"
|
||||||
|
|
||||||
|
if [[ -n ${CAU_MSG_CACHE[$cachekey]+set} ]]; then
|
||||||
|
CAU_MSG_RESULT="${CAU_MSG_CACHE[$cachekey]}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
CAU_MSG_RESULT="$(LC_ALL="$locale" LANGUAGE="${locale%%.*}" gettext -- "$msgid" 2>/dev/null)"
|
||||||
|
[[ -n $CAU_MSG_RESULT ]] || CAU_MSG_RESULT="$msgid"
|
||||||
|
CAU_MSG_CACHE[$cachekey]="$CAU_MSG_RESULT"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# Translations are memoized. Every gettext lookup is a fork, and the settings
|
# Translations are memoized. Every gettext lookup is a fork, and the settings
|
||||||
# screen redraws forty-odd labels per keypress; without this the redraw takes
|
# screen redraws forty-odd labels per keypress; without this the redraw takes
|
||||||
# long enough that a keystroke arriving during it is lost when the terminal
|
# long enough that a keystroke arriving during it is lost when the terminal
|
||||||
|
|||||||
+17
-8
@@ -20,11 +20,17 @@ _cau_config_slurp() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# cau_config_get <Key> [default]
|
# _cau_config_lookup <Key> [default]
|
||||||
cau_config_get() {
|
# Result in CAU_CONFIG_VALUE. Assigning rather than printing matters on the
|
||||||
|
# settings screen, which reads every key on every frame: a command substitution
|
||||||
|
# there is a fork, and forks were the entire cost of a redraw.
|
||||||
|
CAU_CONFIG_VALUE=''
|
||||||
|
|
||||||
|
_cau_config_lookup() {
|
||||||
local key="$1" default="${2:-}" val='' line
|
local key="$1" default="${2:-}" val='' line
|
||||||
|
|
||||||
[[ -r $CAU_CONFIG ]] || { printf '%s\n' "$default"; return; }
|
CAU_CONFIG_VALUE="$default"
|
||||||
|
[[ -r $CAU_CONFIG ]] || return 0
|
||||||
_cau_config_slurp
|
_cau_config_slurp
|
||||||
|
|
||||||
# last assignment wins, matching the previous sed|tail behaviour
|
# last assignment wins, matching the previous sed|tail behaviour
|
||||||
@@ -41,11 +47,14 @@ cau_config_get() {
|
|||||||
val="${val%\"}"
|
val="${val%\"}"
|
||||||
val="${val#\"}"
|
val="${val#\"}"
|
||||||
|
|
||||||
if [[ -n $val ]]; then
|
[[ -n $val ]] && CAU_CONFIG_VALUE="$val"
|
||||||
printf '%s\n' "$val"
|
return 0
|
||||||
else
|
}
|
||||||
printf '%s\n' "$default"
|
|
||||||
fi
|
# cau_config_get <Key> [default]
|
||||||
|
cau_config_get() {
|
||||||
|
_cau_config_lookup "$@"
|
||||||
|
printf '%s\n' "$CAU_CONFIG_VALUE"
|
||||||
}
|
}
|
||||||
|
|
||||||
# cau_config_bool <Key> <default: yes|no>
|
# cau_config_bool <Key> <default: yes|no>
|
||||||
|
|||||||
+72
-34
@@ -156,25 +156,33 @@ _cau_is_true() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# _cau_setting_display <type> <value>
|
# _cau_setting_display <type> <value>
|
||||||
|
# Result in CAU_SETTING_SHOWN. The three constant strings are resolved once by
|
||||||
|
# the caller into CAU_LBL_*; looking them up here would put a translation call
|
||||||
|
# on the per-line path.
|
||||||
|
CAU_SETTING_SHOWN=''
|
||||||
|
CAU_LBL_ON=''
|
||||||
|
CAU_LBL_OFF=''
|
||||||
|
CAU_LBL_NONE=''
|
||||||
|
|
||||||
_cau_setting_display() {
|
_cau_setting_display() {
|
||||||
local type="$1" value="$2"
|
local type="$1" value="$2"
|
||||||
|
|
||||||
case "$type" in
|
case "$type" in
|
||||||
bool)
|
bool)
|
||||||
if _cau_is_true "$value"; then
|
if _cau_is_true "$value"; then
|
||||||
printf '%s%s%s' "$CAU_C_GREEN" "$(cau_msg "ON")" "$CAU_C_RESET"
|
CAU_SETTING_SHOWN="${CAU_C_GREEN}${CAU_LBL_ON}${CAU_C_RESET}"
|
||||||
else
|
else
|
||||||
printf '%s%s%s' "$CAU_C_DIM" "$(cau_msg "OFF")" "$CAU_C_RESET"
|
CAU_SETTING_SHOWN="${CAU_C_DIM}${CAU_LBL_OFF}${CAU_C_RESET}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
text)
|
text)
|
||||||
if [[ -n $value ]]; then
|
if [[ -n $value ]]; then
|
||||||
printf '%s' "$value"
|
CAU_SETTING_SHOWN="$value"
|
||||||
else
|
else
|
||||||
printf '%s%s%s' "$CAU_C_DIM" "$(cau_msg "(none)")" "$CAU_C_RESET"
|
CAU_SETTING_SHOWN="${CAU_C_DIM}${CAU_LBL_NONE}${CAU_C_RESET}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*) printf '%s' "$value" ;;
|
*) CAU_SETTING_SHOWN="$value" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,58 +213,88 @@ _cau_setting_cycle() {
|
|||||||
# cau_ui_settings
|
# cau_ui_settings
|
||||||
# A cursor list rather than a numbered menu: there are eighteen settings, and
|
# A cursor list rather than a numbered menu: there are eighteen settings, and
|
||||||
# numbering them would run out of digits and force paging.
|
# numbering them would run out of digits and force paging.
|
||||||
|
#
|
||||||
|
# The frame is assembled in memory and written once. Everything constant - the
|
||||||
|
# specs, the translated labels, the clear sequence - is resolved before the
|
||||||
|
# loop, and the values are re-read only after something actually changes.
|
||||||
|
# Drawing the naive way cost a command substitution per label per frame, which
|
||||||
|
# measured 435 ms per keypress: arrow keys felt like the console was reloading,
|
||||||
|
# because in effect it was.
|
||||||
cau_ui_settings() {
|
cau_ui_settings() {
|
||||||
local cursor=0 key spec name type default label value line pad
|
|
||||||
local count=${#CAU_SETTINGS[@]}
|
local count=${#CAU_SETTINGS[@]}
|
||||||
|
local -a names=() types=() defaults=() labels=()
|
||||||
|
local -a values=()
|
||||||
|
local spec name type default label locale i key frame row pad dirty=1 cursor=0
|
||||||
|
|
||||||
while true; do
|
locale="$(cau_ui_locale)"
|
||||||
clear 2>/dev/null || true
|
cau_msg_into "$locale" "ON"; CAU_LBL_ON="$CAU_MSG_RESULT"
|
||||||
cau_head " $(cau_msg "Settings")"
|
cau_msg_into "$locale" "OFF"; CAU_LBL_OFF="$CAU_MSG_RESULT"
|
||||||
|
cau_msg_into "$locale" "(none)"; CAU_LBL_NONE="$CAU_MSG_RESULT"
|
||||||
|
|
||||||
local i=0
|
|
||||||
for spec in "${CAU_SETTINGS[@]}"; do
|
for spec in "${CAU_SETTINGS[@]}"; do
|
||||||
IFS='|' read -r name type default label <<< "$spec"
|
IFS='|' read -r name type default label <<< "$spec"
|
||||||
value="$(cau_config_get "$name" "$default")"
|
names+=("$name"); types+=("$type"); defaults+=("$default")
|
||||||
|
cau_msg_into "$locale" "$label"
|
||||||
if (( i == cursor )); then
|
labels+=("$CAU_MSG_RESULT")
|
||||||
line="${CAU_C_BLUE}▸${CAU_C_RESET} "
|
|
||||||
else
|
|
||||||
line=" "
|
|
||||||
fi
|
|
||||||
|
|
||||||
local text
|
|
||||||
text="$(cau_msg "$label")"
|
|
||||||
pad=$(( 42 - ${#text} ))
|
|
||||||
(( pad < 0 )) && pad=0
|
|
||||||
|
|
||||||
printf ' %s%s%*s %s\n' "$line" "$text" "$pad" '' \
|
|
||||||
"$(_cau_setting_display "$type" "$value")"
|
|
||||||
i=$(( i + 1 ))
|
|
||||||
done
|
done
|
||||||
|
|
||||||
printf '\n %s%s%s\n' "$CAU_C_DIM" \
|
local title hint
|
||||||
"$(cau_msg "Up/Down select - Space or Right changes - q goes back")" "$CAU_C_RESET"
|
cau_msg_into "$locale" "Settings"; title="$CAU_MSG_RESULT"
|
||||||
|
cau_msg_into "$locale" "Up/Down select - Space or Right changes - q goes back"
|
||||||
|
hint="$CAU_MSG_RESULT"
|
||||||
|
|
||||||
|
# the terminfo clear string, fetched once instead of forking per frame
|
||||||
|
local clearseq
|
||||||
|
clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J'
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if (( dirty )); then
|
||||||
|
for i in "${!names[@]}"; do
|
||||||
|
_cau_config_lookup "${names[i]}" "${defaults[i]}"
|
||||||
|
values[i]="$CAU_CONFIG_VALUE"
|
||||||
|
done
|
||||||
|
dirty=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
frame="$clearseq"$'\n'"${CAU_C_BOLD}${CAU_C_BLUE} ${title}${CAU_C_RESET}"$'\n\n'
|
||||||
|
|
||||||
|
local marker selected="${CAU_C_BLUE}▸${CAU_C_RESET} "
|
||||||
|
for i in "${!names[@]}"; do
|
||||||
|
_cau_setting_display "${types[i]}" "${values[i]}"
|
||||||
|
pad=$(( 42 - ${#labels[i]} ))
|
||||||
|
(( pad < 0 )) && pad=0
|
||||||
|
if (( i == cursor )); then marker="$selected"; else marker=' '; fi
|
||||||
|
printf -v row ' %s%s%*s %s' \
|
||||||
|
"$marker" "${labels[i]}" "$pad" '' "$CAU_SETTING_SHOWN"
|
||||||
|
frame+="$row"$'\n'
|
||||||
|
done
|
||||||
|
|
||||||
|
frame+=$'\n'" ${CAU_C_DIM}${hint}${CAU_C_RESET}"$'\n'
|
||||||
|
printf '%s' "$frame"
|
||||||
|
|
||||||
key="$(cau_read_key)" || return 0
|
key="$(cau_read_key)" || return 0
|
||||||
|
|
||||||
IFS='|' read -r name type default label <<< "${CAU_SETTINGS[cursor]}"
|
type="${types[cursor]}"
|
||||||
value="$(cau_config_get "$name" "$default")"
|
name="${names[cursor]}"
|
||||||
|
|
||||||
case "$key" in
|
case "$key" in
|
||||||
up|k) cursor=$(( (cursor - 1 + count) % count )) ;;
|
up|k) cursor=$(( (cursor - 1 + count) % count )) ;;
|
||||||
down|j) cursor=$(( (cursor + 1) % count )) ;;
|
down|j) cursor=$(( (cursor + 1) % count )) ;;
|
||||||
space|enter|right|l)
|
space|enter|right|l)
|
||||||
if [[ $type == text ]]; then
|
if [[ $type == text ]]; then
|
||||||
cau_ui_edit_text "$name" "$value"
|
cau_ui_edit_text "$name" "${values[cursor]}"
|
||||||
else
|
else
|
||||||
cau_config_set "$name" "$(_cau_setting_cycle "$type" "$value" 1)" \
|
cau_config_set "$name" "$(_cau_setting_cycle "$type" "${values[cursor]}" 1)" \
|
||||||
|| { cau_bad "$(cau_msg "Could not write the configuration file.")"; cau_pause; }
|
|| { cau_bad "$(cau_msg "Could not write the configuration file.")"; cau_pause; }
|
||||||
fi
|
fi
|
||||||
|
dirty=1
|
||||||
;;
|
;;
|
||||||
left|h)
|
left|h)
|
||||||
[[ $type == text ]] || cau_config_set "$name" \
|
if [[ $type != text ]]; then
|
||||||
"$(_cau_setting_cycle "$type" "$value" -1)" \
|
cau_config_set "$name" "$(_cau_setting_cycle "$type" "${values[cursor]}" -1)" \
|
||||||
|| { cau_bad "$(cau_msg "Could not write the configuration file.")"; cau_pause; }
|
|| { cau_bad "$(cau_msg "Could not write the configuration file.")"; cau_pause; }
|
||||||
|
dirty=1
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
q|Q|escape) return 0 ;;
|
q|Q|escape) return 0 ;;
|
||||||
*) ;;
|
*) ;;
|
||||||
|
|||||||
Reference in new issue
Block a user