From 4798d76b5148382da04f98b42604f7bc2a02808a Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Thu, 27 Aug 2026 12:14:51 +0800 Subject: [PATCH] perf: gate resize and post-resize interactions --- examples/research-shelf.el | 36 ++--- scripts/benchmark-research-shelf.el | 190 +++++++++++++++++++++++- tests/benchmark-research-shelf-tests.el | 4 +- 3 files changed, 210 insertions(+), 20 deletions(-) diff --git a/examples/research-shelf.el b/examples/research-shelf.el index 0eaf60e..fd01b8f 100644 --- a/examples/research-shelf.el +++ b/examples/research-shelf.el @@ -456,14 +456,14 @@ constructed. When omitted, initialize it for direct programmatic callers." (defun etaf-research-shelf--filter-view (model) "Return the filter rail for MODEL and inherited filter Context." (etaf-view - (box :layout 'column + (column :class "research-shelf-filter-rail" :width 'stretch :min-width 0 :flex-grow 1 :flex-shrink 1 :flex-basis '(220) :padding '(1 1) :border (etaf-theme-token :line) :color (etaf-theme-token :ink) :bgcolor (etaf-theme-token :panel) - (label :text "Library" :face 'bold) + (label :text "Library" :font-weight 'bold) (label :text "VIEWS" :color (etaf-theme-token :muted)) (etaf-research-shelf-filter-button :filter-key 'all :fallback "All" :ref 'research-shelf-filter-all) @@ -492,18 +492,18 @@ constructed. When omitted, initialize it for direct programmatic callers." "Return the reading list for MODEL and static MAIN FORM." (let ((controller (etaf-research-shelf--controller model))) (etaf-view - (box :layout 'column + (column :class "research-shelf-list" :width 'stretch :min-width 0 :flex-grow 4 :flex-shrink 1 :flex-basis '(620) :padding '(1 2) :border (etaf-theme-token :line) :bgcolor (etaf-theme-token :panel) - (box :layout 'flex + (flex :width 'stretch :flex-wrap 'wrap :align-items 'center :gap '(1 (8)) - (box :layout 'column + (column :flex-grow 1 :flex-shrink 1 :flex-basis '(0) :min-width 0 (label :text (etaf-playground-static-value - form :title "Reading queue") :face 'bold) + form :title "Reading queue") :font-weight 'bold) (label :text (format "%d items ยท SQLite-backed" (or (etaf-value (etaf-data-total controller)) 0)) :color (etaf-theme-token :muted))) @@ -539,7 +539,7 @@ constructed. When omitted, initialize it for direct programmatic callers." (defun etaf-research-shelf--detail-view (model form) "Return the selected record inspector for MODEL and static FORM." (etaf-view - (box :layout 'column + (column :class "research-shelf-detail" :width 'stretch :min-width 0 :flex-grow 2 :flex-shrink 1 :flex-basis '(340) :padding '(2 2) @@ -548,7 +548,7 @@ constructed. When omitted, initialize it for direct programmatic callers." (label :text (etaf-playground-static-value form :title "Selected item") :color (etaf-theme-token :accent)) - (text :face 'bold + (text :font-weight 'bold (expr :value (if-let* ((row (etaf-research-shelf--selected model))) (plist-get row :title) @@ -560,7 +560,7 @@ constructed. When omitted, initialize it for direct programmatic callers." (plist-get row :kind)) "Select one row to inspect it."))) (box :height 1) - (text :face 'bold + (text :font-weight 'bold (expr :value (if-let* ((row (etaf-research-shelf--selected model))) (etaf-research-shelf--status-label (plist-get row :status)) @@ -598,7 +598,7 @@ constructed. When omitted, initialize it for direct programmatic callers." (finished (and row (equal (plist-get row :status) "finished"))) (archived (and row (equal (plist-get row :status) "archived")))) (etaf-view - (box :layout 'flex + (flex :width 'stretch :flex-wrap 'wrap :gap '(1 (6)) (button :label "+ 10%" :ref 'research-shelf-progress :variant 'secondary @@ -649,21 +649,21 @@ constructed. When omitted, initialize it for direct programmatic callers." (header-spec (etaf-inject 'research-shelf-header-spec nil t))) (lambda () (etaf-view - (box :layout 'flex + (flex :class "research-shelf-header" :width 'stretch :min-width 0 :flex-wrap 'wrap :align-items 'center :gap '(1 (10)) - (box :layout 'column + (column :flex-grow 1 :flex-shrink 1 :flex-basis '(0) :min-width 0 (label :text (etaf-playground-static-value header-spec :eyebrow "Research Shelf") :color (etaf-theme-token :accent)) (label :text (etaf-playground-static-value - root-spec :title "Research Shelf") :face 'bold) + root-spec :title "Research Shelf") :font-weight 'bold) (label :text (etaf-playground-static-value root-spec :subtitle "A quiet place for unfinished ideas") :color (etaf-theme-token :muted))) - (box :layout 'column :width 'max-content :min-width 0 + (column :width 'max-content :min-width 0 :flex-grow 0 :flex-shrink 0 (checkbox :label "Dark" :ref 'research-shelf-theme-toggle :checked (plist-get model :dark) @@ -679,10 +679,10 @@ constructed. When omitted, initialize it for direct programmatic callers." (footer-spec (etaf-inject 'research-shelf-footer-spec nil t))) (lambda () (etaf-view - (box :layout 'flex + (flex :class "research-shelf-footer" :width 'stretch :flex-wrap 'wrap :gap '(1 (8)) - (box :layout 'column + (column :flex-grow 1 :flex-shrink 1 :flex-basis '(0) :min-width 0 (label :text (plist-get model :toast)) (label :text (etaf-playground-static-value @@ -697,14 +697,14 @@ constructed. When omitted, initialize it for direct programmatic callers." The App supplies `header', `filters', `library', `detail', and `footer'; each slot has a reusable built-in fallback for direct composition." :view - (box :layout 'column + (column :class "research-shelf-shell" :width '(viewport) :height 'auto :min-height '(viewport-height) :color (etaf-theme-token :ink) :bgcolor (etaf-theme-token :paper) (slot :name 'header (etaf-research-shelf-header)) - (box :layout 'flex + (flex :class "research-shelf-workspace" :width 'stretch :flex-wrap 'wrap :align-items 'stretch :align-content 'start :gap '(1 (6)) diff --git a/scripts/benchmark-research-shelf.el b/scripts/benchmark-research-shelf.el index 644f71f..981b05e 100644 --- a/scripts/benchmark-research-shelf.el +++ b/scripts/benchmark-research-shelf.el @@ -22,7 +22,8 @@ (defconst etaf-performance-evaluator-sample-count 20) (defconst etaf-performance-evaluator-latency-budget-ms 50.0) (defconst etaf-performance-evaluator-scenario-budgets-ms - '(("theme-toggle" :p95 90.0 :max 110.0)) + '(("theme-toggle" :p95 90.0 :max 110.0) + ("post-resize-theme-toggle" :p95 90.0 :max 110.0)) "User-approved scenario budgets overriding the default latency budget.") (defconst etaf-performance-evaluator-overhead-budget-ms 2.0) (defconst etaf-performance-evaluator-fixture-size 256) @@ -342,6 +343,174 @@ never included in SAMPLES. ACTION's result is passed to VERIFY and CLEANUP." failures)) (nreverse failures)))) +(defun etaf-performance-evaluator--visible-item-count (buffer) + "Return the item count rendered in BUFFER, or nil." + (let ((text (etaf-performance-evaluator--text buffer))) + (when (string-match "\\([0-9]+\\) items" text) + (string-to-number (match-string 1 text))))) + +(defun etaf-performance-evaluator--visible-page (buffer) + "Return BUFFER's rendered `(CURRENT . TOTAL)' page pair, or nil." + (let ((text (etaf-performance-evaluator--text buffer))) + (when (string-match "Page \\([0-9]+\\) / \\([0-9]+\\)" text) + (cons (string-to-number (match-string 1 text)) + (string-to-number (match-string 2 text)))))) + +(cl-defun etaf-performance-evaluator--measure-event + (label runtime buffer action verify &key setup) + "Measure one public event ACTION under LABEL and run VERIFY. +ACTION and VERIFY receive the sample index. Every sample must advance the +ETAF generation and preserve the mounted surface invariant." + (etaf-performance-evaluator--measure + label + (lambda (index) + (let ((before (etaf-runtime-generation runtime))) + (list before + (progn (funcall action index) + (etaf-runtime-generation runtime))))) + (lambda (result index) + (unless (> (car result) -1) + (error "%s: invalid source generation" label)) + (unless (> (cadr result) (car result)) + (error "%s: event did not advance the generation" label)) + (funcall verify index) + (etaf-performance-evaluator--surface-invariant runtime buffer label)) + :setup setup)) + +(defun etaf-performance-evaluator--measure-add + (label runtime buffer) + "Measure the public Add action under LABEL." + (let (before-count) + (etaf-performance-evaluator--measure-event + label runtime buffer + (lambda (_index) + (setq before-count + (etaf-performance-evaluator--visible-item-count buffer)) + (etaf-dispatch-event runtime 'research-shelf-add 'press)) + (lambda (_index) + (unless (and before-count + (= (1+ before-count) + (etaf-performance-evaluator--visible-item-count buffer)) + (etaf-performance-evaluator--visible-match-p + buffer "Added to your shelf")) + (error "%s: insertion/count/toast invariant failed" label)))))) + +(defun etaf-performance-evaluator--measure-reload + (label runtime buffer) + "Measure the public Reload action under LABEL." + (etaf-performance-evaluator--measure-event + label runtime buffer + (lambda (_index) + (etaf-dispatch-event runtime 'research-shelf-reload 'press)) + (lambda (_index) + (unless (etaf-performance-evaluator--visible-match-p + buffer "Library reloaded") + (error "%s: reload toast invariant failed" label))))) + +(defun etaf-performance-evaluator--measure-viewport-resize + (runtime buffer) + "Measure alternating wide/compact viewport publications for BUFFER." + (let ((target-buffer (get-buffer buffer))) + (unless (buffer-live-p target-buffer) + (error "Viewport-resize: target buffer is not live")) + (etaf-performance-evaluator--measure + "viewport-resize" + (lambda (index) + (let* ((target (if (zerop (% (1+ index) 2)) 720 1413)) + (report + (ebox-rerender-buffer-with-context target-buffer target 62))) + (list target report))) + (lambda (result _index) + (let* ((target (car result)) + (report (cadr result))) + (unless (and (= (plist-get report :target-viewport-width) target) + (eq (plist-get report :projection-kind) 'native-frame) + (plist-get report :runtime-published) + (not (plist-get report :tp-full-root)) + (not (plist-get report :tp-scope-fallback))) + (error "Viewport-resize: retained native publication failed")) + (etaf-performance-evaluator--surface-invariant + runtime buffer "viewport-resize")))))) + +(defun etaf-performance-evaluator--measure-post-resize-events + (runtime buffer) + "Return post-resize row/theme/filter/page/Add/Reload measurements." + (let (results) + (push + (etaf-performance-evaluator--measure-event + "post-resize-row-selection" runtime buffer + (lambda (index) + (etaf-performance-evaluator--select-ref + runtime (if (zerop (% (1+ index) 2)) + 'research-shelf-row-1 + 'research-shelf-row-2))) + (lambda (index) + (let ((ref (if (zerop (% (1+ index) 2)) + 'research-shelf-row-1 + 'research-shelf-row-2))) + (unless (string-match-p + "selected" + (or (plist-get + (etaf-runtime-host-props-for runtime ref) :class) + "")) + (error "Post-resize row selection invariant failed"))))) + results) + (push + (etaf-performance-evaluator--measure-event + "post-resize-theme-toggle" runtime buffer + (lambda (_index) + (etaf-dispatch-event runtime 'research-shelf-theme-toggle 'press)) + (lambda (_index) + (unless (etaf-performance-evaluator--visible-match-p + buffer "\\(Dark theme\\|Light theme\\)") + (error "Post-resize theme invariant failed")))) + results) + (push + (etaf-performance-evaluator--measure-event + "post-resize-filter-query" runtime buffer + (lambda (index) + (etaf-dispatch-event + runtime + (if (zerop (% (1+ index) 2)) + 'research-shelf-filter-reading + 'research-shelf-filter-all) + 'press)) + (lambda (index) + (unless (etaf-performance-evaluator--visible-match-p + buffer + (if (zerop (% (1+ index) 2)) + "Showing Reading" + "Showing All")) + (error "Post-resize filter invariant failed")))) + results) + (etaf-dispatch-event runtime 'research-shelf-filter-all 'press) + (push + (etaf-performance-evaluator--measure-event + "post-resize-pagination" runtime buffer + (lambda (_index) + (pcase-let ((`(,current . ,_total) + (etaf-performance-evaluator--visible-page buffer))) + (etaf-dispatch-event + runtime + (if (= current 1) + 'research-shelf-page-next + 'research-shelf-page-previous) + 'press))) + (lambda (_index) + (unless (etaf-performance-evaluator--visible-page buffer) + (error "Post-resize pagination invariant failed")))) + results) + (when-let* ((page (etaf-performance-evaluator--visible-page buffer))) + (unless (= (car page) 1) + (etaf-dispatch-event runtime 'research-shelf-page-previous 'press))) + (push (etaf-performance-evaluator--measure-add + "post-resize-add-reading" runtime buffer) + results) + (push (etaf-performance-evaluator--measure-reload + "post-resize-reload" runtime buffer) + results) + (nreverse results))) + (defun etaf-performance-evaluator-run () "Run the fixed cross-package evaluator and return non-nil on success." (etaf-performance-evaluator--load-workload) @@ -502,6 +671,23 @@ never included in SAMPLES. ACTION's result is passed to VERIFY and CLEANUP." runtime buffer "focus-navigation"))) results) + (push + (etaf-performance-evaluator--measure-add + "add-reading" runtime buffer) + results) + (push + (etaf-performance-evaluator--measure-reload + "reload" runtime buffer) + results) + (push + (etaf-performance-evaluator--measure-viewport-resize + runtime buffer) + results) + (dolist (result + (etaf-performance-evaluator--measure-post-resize-events + runtime buffer)) + (push result results)) + ;; Capture one representative cross-package trace after the latency ;; samples. This validates coverage without folding observer work ;; into the product latency distribution. @@ -517,6 +703,8 @@ never included in SAMPLES. ACTION's result is passed to VERIFY and CLEANUP." (etaf-dispatch-event runtime 'research-shelf-filter-all 'press) (etaf-dispatch-event runtime 'research-shelf-page-next 'press) (etaf-dispatch-event runtime 'research-shelf-page-previous 'press) + (etaf-dispatch-event runtime 'research-shelf-add 'press) + (etaf-dispatch-event runtime 'research-shelf-reload 'press) (etaf-performance-evaluator--select-ref runtime 'research-shelf-row-2) (etaf-dispatch-event runtime 'research-shelf-progress 'press) diff --git a/tests/benchmark-research-shelf-tests.el b/tests/benchmark-research-shelf-tests.el index 49c188a..9cbdfe2 100644 --- a/tests/benchmark-research-shelf-tests.el +++ b/tests/benchmark-research-shelf-tests.el @@ -69,7 +69,9 @@ (should-not (etaf-performance-evaluator--latency-failures '(("theme-toggle" :count 20 :min 80.0 :p50 84.0 - :p95 86.0 :max 99.0)))) + :p95 86.0 :max 99.0) + ("post-resize-theme-toggle" :count 20 :min 80.0 :p50 84.0 + :p95 86.0 :max 99.0)))) (should (= 2 (length (etaf-performance-evaluator--latency-failures