perf: verify continuous Flex viewport delivery

This commit is contained in:
Kinneyzhang 2026-08-26 00:10:12 +08:00
parent 256868a7ee
commit 5d1e38856f
6 changed files with 94 additions and 37 deletions

View File

@ -3,7 +3,7 @@ ECSS_DIR ?= ../ecss
TP_DIR ?= ../tp
LOAD_PATH = -L . -L ../ebox -L $(ECSS_DIR) -L $(TP_DIR)
.PHONY: all compile test check checkdoc load clean
.PHONY: all compile test check checkdoc load performance-evaluator clean
all: check
@ -21,6 +21,9 @@ load: compile
$(EMACS) -Q --batch $(LOAD_PATH) --eval '(require (quote ebox-playground))' \
--eval '(princ "ebox-playground load OK\n")'
performance-evaluator:
EMACS="$(EMACS)" ./scripts/flex-resize-evaluator.sh
checkdoc:
$(EMACS) -Q --batch --eval '(progn (require (quote checkdoc)) (dolist (file (directory-files "." t)) (when (string-suffix-p ".el" file) (checkdoc-file file))))'

View File

@ -6,7 +6,7 @@ ECSS 0.1.0 and TP 1.0.0 are independent packages and may be installed in either
Standalone gallery entry points default to a definite 720 px canvas so nested examples have stable space in batch renders and one-window demos. Split-window `.ebox` previews instead bind `(viewport)` to the preview window's display-safe width, so viewport-based separators and sections fit the right-hand pane. `etaf-playground` remains the separate higher-level Component and Runtime showcase.
Standalone and batch fixture renders use the same compact 720 px default viewport unless the caller dynamically binds `ebox-viewport-width`; viewport-based references therefore stay inside the example canvas. When `C-c C-c` renders an `.ebox` source buffer, the command keeps the source on the left, opens the preview on the right, and binds `(viewport)` to Ebox's shared display-safe window width. Continuous window changes are coalesced by Ebox's single viewport controller; the Playground owns no second resize hook or timer.
Standalone and batch fixture renders use the same compact 720 px default viewport unless the caller dynamically binds `ebox-viewport-width`; viewport-based references therefore stay inside the example canvas. When `C-c C-c` renders an `.ebox` source buffer, the command keeps the source on the left, opens the preview on the right, and binds `(viewport)` to Ebox's shared display-safe window width. Ebox's single serialized viewport controller publishes every window change immediately through its retained path; the Playground owns no second resize hook or timer.
```elisp
(require 'ebox-playground)

View File

@ -6,7 +6,7 @@ ECSS 0.1.0 与 TP 1.0.0 是互相独立的包,安装顺序任意;两者都
独立画廊入口默认使用明确的 720 px 画布,因此 batch 渲染和单窗口演示里的嵌套示例有稳定空间。分屏 `.ebox` 预览则会把 `(viewport)` 绑定为右侧预览窗口的显示安全宽度,所以依赖 viewport 的分隔条和区块会适配右侧窗格。高层 Component/Runtime 展示仍由独立的 `etaf-playground` 提供。
独立和 batch fixture 渲染默认也使用紧凑的 720 px 视口;如果调用方动态绑定了 `ebox-viewport-width`,则保留调用方的值。因此依赖 viewport 的参考例子也会保持在示例画布内。通过 `C-c C-c` 渲染 `.ebox` 源文件时,命令会把源文件保留在左侧、在右侧打开预览,并将 `(viewport)` 绑定为 Ebox 统一计算的显示安全宽度;连续 window-size 变化由 Ebox 唯一的 viewport controller 合并Playground 不再拥有第二套 resize hook 或 timer。
独立和 batch fixture 渲染默认也使用紧凑的 720 px 视口;如果调用方动态绑定了 `ebox-viewport-width`,则保留调用方的值。因此依赖 viewport 的参考例子也会保持在示例画布内。通过 `C-c C-c` 渲染 `.ebox` 源文件时,命令会把源文件保留在左侧、在右侧打开预览,并将 `(viewport)` 绑定为 Ebox 统一计算的显示安全宽度;Ebox 唯一且串行的 viewport controller 会立即通过 retained 路径发布每次 window-size 变化Playground 不再拥有第二套 resize hook 或 timer。
```elisp
(require 'ebox-playground)

View File

@ -6,7 +6,8 @@
;; This evaluator exercises the public playground path used by an interactive
;; `.ebox' preview: source buffer on the left, rendered buffer on the right,
;; and a real window resize that reaches `window-size-change-functions'.
;; and a sustained window-resize burst that reaches
;; `window-size-change-functions'.
;;; Code:
@ -23,6 +24,16 @@
:type 'number
:group 'ebox-playground)
(defcustom ebox-playground-flex-resize-evaluator-max-update-seconds 0.05
"Maximum allowed time for one published viewport update."
:type 'number
:group 'ebox-playground)
(defcustom ebox-playground-flex-resize-evaluator-max-gap-seconds 0.08
"Maximum allowed completion gap during the resize burst."
:type 'number
:group 'ebox-playground)
(defvar ebox-playground-flex-resize-evaluator--output-lines nil
"Report lines collected during one evaluator run.")
@ -87,14 +98,14 @@ than charging initial frame settling to resize."
(defun ebox-playground-flex-resize-evaluator-run ()
"Run the real GUI flex preview resize evaluator.
The function returns non-nil only when one splitter resize schedules exactly
one Ebox viewport update, publishes valid output, and stays under the timing
threshold. It leaves the normal idle-prewarm configuration untouched; the
daemon evaluator does not enable background prewarm, keeping the measured
action free of concurrent cache-warming CPU work."
The function returns non-nil only when a sustained splitter resize burst uses
Ebox's immediate serialized viewport hook, publishes every intermediate and
final result through the retained path, and stays under the timing threshold.
It leaves the normal idle prewarm configuration untouched; the daemon evaluator
does not enable background prewarm, keeping the measured action free of
concurrent cache warming CPU work."
(let ((ebox-render-gc-cons-threshold nil)
(ebox-render-gc-cons-percentage nil)
(ebox-viewport-resize-delay 0.05)
(source nil)
(preview nil)
(preview-window nil)
@ -102,6 +113,10 @@ action free of concurrent cache-warming CPU work."
(resize-count 0)
(resize-elapsed 0.0)
(resize-finished nil)
(action-start nil)
(last-event-elapsed nil)
(resize-publications nil)
(resize-durations nil)
(completed-without-error nil)
(ebox-playground-flex-resize-evaluator--output-lines nil)
(checks nil))
@ -130,22 +145,40 @@ action free of concurrent cache-warming CPU work."
(let ((start (float-time)))
(unwind-protect
(apply original arguments)
(setq resize-elapsed
(+ resize-elapsed (- (float-time) start)))
(let ((duration (- (float-time) start)))
(setq resize-elapsed (+ resize-elapsed duration))
(push duration resize-durations))
(when action-start
(push (- (float-time) action-start)
resize-publications))
(setq resize-finished t)))))
(advice-add 'ebox-rerender-buffer-with-context
:around resize-advice)
(let* ((start (float-time))
(old-width
(ebox-playground--window-viewport-width preview-window))
(delta -10)
(deadline (+ start 15.0)))
(window-resize preview-window delta t)
(while (and (not resize-finished)
(< (float-time) deadline))
(ebox-playground--window-viewport-width preview-window)))
(setq action-start start)
(dolist (delta '(-4 2 -4 2 -4 2))
(setq last-event-elapsed (- (float-time) start))
(window-resize preview-window delta t)
(redisplay t)
(sleep-for 0.01))
(sleep-for 0.02))
(let* ((elapsed (- (float-time) start))
(publication-times
(sort (copy-sequence resize-publications) #'<))
(publication-gaps
(cl-loop for tail on publication-times
while (cdr tail)
collect (- (cadr tail) (car tail))))
(first-publication (car publication-times))
(last-publication (car (last publication-times)))
(max-publication
(if resize-durations (apply #'max resize-durations) 0.0))
(max-gap
(if publication-gaps (apply #'max publication-gaps) 0.0))
(final-latency
(and last-publication
(- last-publication last-event-elapsed)))
(viewport-width
(ebox-playground--window-viewport-width preview-window))
(report (ebox-buffer-update-report preview))
@ -203,10 +236,39 @@ action free of concurrent cache-warming CPU work."
(window-live-p preview-window))
(format "source=%S preview=%S" source preview))
(ebox-playground-flex-resize-evaluator--check
"one resize update"
(= resize-count 1)
(format "ebox-rerender-buffer-with-context=%d"
resize-count))
"intermediate resize updates"
(and (>= resize-count 4)
first-publication
(<= first-publication
ebox-playground-flex-resize-evaluator-max-update-seconds)
(< first-publication last-event-elapsed))
(format "updates=%d first=%.6fs last-event=%.6fs"
resize-count
(or first-publication 0.0)
last-event-elapsed))
(ebox-playground-flex-resize-evaluator--check
"per-update resize budget"
(<= max-publication
ebox-playground-flex-resize-evaluator-max-update-seconds)
(format "max=%.6fs threshold=%.6fs"
max-publication
ebox-playground-flex-resize-evaluator-max-update-seconds))
(ebox-playground-flex-resize-evaluator--check
"continuous resize cadence"
(<= max-gap
ebox-playground-flex-resize-evaluator-max-gap-seconds)
(format "max-gap=%.6fs threshold=%.6fs"
max-gap
ebox-playground-flex-resize-evaluator-max-gap-seconds))
(ebox-playground-flex-resize-evaluator--check
"final resize latency"
(and final-latency
(>= final-latency 0)
(<= final-latency
ebox-playground-flex-resize-evaluator-max-update-seconds))
(format "final=%.6fs threshold=%.6fs"
(or final-latency -1.0)
ebox-playground-flex-resize-evaluator-max-update-seconds))
(ebox-playground-flex-resize-evaluator--check
"resize completed"
resize-finished
@ -249,8 +311,10 @@ action free of concurrent cache-warming CPU work."
elapsed
ebox-playground-flex-resize-evaluator-max-seconds))))
(push (format
"RESIZE old-viewport-px=%S new-viewport-px=%S total=%.6fs rerender=%.6fs strategy=%S projection=%S axes=%S reconciled=%S native-flex=%S tp-ops=%S cache-hits=%S cache-misses=%S"
"RESIZE old-viewport-px=%S new-viewport-px=%S total=%.6fs rerender=%.6fs first=%.6fs max-update=%.6fs max-gap=%.6fs final=%.6fs strategy=%S projection=%S axes=%S reconciled=%S native-flex=%S tp-ops=%S cache-hits=%S cache-misses=%S"
old-width viewport-width elapsed resize-elapsed
(or first-publication 0.0) max-publication max-gap
(or final-latency -1.0)
(plist-get report :strategy) projection-kind
(plist-get report :viewport-axes)
(plist-get report :reconciled-objects)

View File

@ -15,7 +15,7 @@ trap cleanup EXIT INT TERM
# that directly affect this render path. Loading the complete user init would
# add unrelated packages, timers, and UI state to the performance gate.
"$emacs" -Q --daemon="$server" \
-L "$root" -L "$root/../ebox" -L "$root/../new-architecture/ecss" \
-L "$root" -L "$root/../ebox" -L "$root/../ecss" \
-L "$root/../tp" \
--eval '(setq load-prefer-newer t
gc-cons-threshold most-positive-fixnum

View File

@ -305,8 +305,7 @@
(ert-deftest ebox-playground-preview-resizes-through-ebox-viewport-update ()
"A preview resize should publish an incremental Ebox viewport update."
(let ((preview "*Ebox Preview: /tmp/ebox-playground-viewport-resize.ebox*")
(window-width 987)
(ebox-viewport-resize-delay 0))
(window-width 987))
(unwind-protect
(cl-letf (((symbol-function 'window-body-width)
(lambda (&optional _window _pixelwise) window-width))
@ -323,16 +322,7 @@
(should (memq #'ebox--window-size-change
window-size-change-functions))
(let ((noninteractive nil))
(ebox--window-size-change (window-frame window)))
;; Batch Emacs does not service idle timers from `sit-for'.
(let ((pending
(gethash (current-buffer)
ebox--viewport-resize-pending-table)))
(should pending)
(when (timerp (aref pending 4))
(cancel-timer (aref pending 4)))
(ebox--viewport-resize-apply
(current-buffer) (aref pending 0)))))
(ebox--window-size-change (window-frame window)))))
(with-current-buffer preview
(should (= (ebox-playground-test--max-line-width
(buffer-string))