From 5d1e38856ff64610629ac2013e310e22f91eec6b Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Wed, 26 Aug 2026 00:10:12 +0800 Subject: [PATCH] perf: verify continuous Flex viewport delivery --- Makefile | 5 +- README.md | 2 +- README.zh-CN.md | 2 +- .../ebox-playground-flex-resize-evaluator.el | 106 ++++++++++++++---- scripts/flex-resize-evaluator.sh | 2 +- tests/ebox-playground-tests.el | 14 +-- 6 files changed, 94 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index 6a83369..50b96e1 100644 --- a/Makefile +++ b/Makefile @@ -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))))' diff --git a/README.md b/README.md index 0a8ff9f..272c3b6 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/README.zh-CN.md b/README.zh-CN.md index 5e4b583..5e9e4fd 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -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) diff --git a/scripts/ebox-playground-flex-resize-evaluator.el b/scripts/ebox-playground-flex-resize-evaluator.el index bde93f8..d2c967b 100644 --- a/scripts/ebox-playground-flex-resize-evaluator.el +++ b/scripts/ebox-playground-flex-resize-evaluator.el @@ -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) diff --git a/scripts/flex-resize-evaluator.sh b/scripts/flex-resize-evaluator.sh index 4e6f1b0..9c28b0d 100755 --- a/scripts/flex-resize-evaluator.sh +++ b/scripts/flex-resize-evaluator.sh @@ -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 diff --git a/tests/ebox-playground-tests.el b/tests/ebox-playground-tests.el index e86250d..b223091 100644 --- a/tests/ebox-playground-tests.el +++ b/tests/ebox-playground-tests.el @@ -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))