ebox-playground/scripts/ebox-playground-flex-resize-evaluator.el
2026-08-25 22:45:23 +08:00

292 lines
14 KiB
EmacsLisp

;;; ebox-playground-flex-resize-evaluator.el --- Flex resize evaluator -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; 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'.
;;; Code:
(require 'cl-lib)
(require 'ebox-playground)
(defconst ebox-playground-flex-resize-evaluator--fixture
(expand-file-name "examples/flex-reference.ebox"
ebox-playground-directory)
"Flex fixture used by the performance evaluator.")
(defcustom ebox-playground-flex-resize-evaluator-max-seconds 0.5
"Maximum allowed end-to-end time for one settled preview resize."
:type 'number
:group 'ebox-playground)
(defvar ebox-playground-flex-resize-evaluator--output-lines nil
"Report lines collected during one evaluator run.")
(defconst ebox-playground-flex-resize-evaluator--runtime-properties
(delete-dups
(append (mapcar #'cdr ebox-region-types)
'(ebox-content-idx ebox-content-owners ebox-scroll-window)))
"Ebox identity properties omitted from fresh-render comparison.")
(defun ebox-playground-flex-resize-evaluator--preview-buffer ()
"Return the preview buffer for the fixed flex fixture."
(get-buffer
(format "*Ebox Preview: %s*"
ebox-playground-flex-resize-evaluator--fixture)))
(defun ebox-playground-flex-resize-evaluator--line-widths (buffer)
"Return pixel widths for every rendered line in BUFFER."
(with-current-buffer buffer
(mapcar #'ebox-string-pixel-width
(ebox-string-lines (buffer-string)))))
(defun ebox-playground-flex-resize-evaluator--visual-text (text)
"Return TEXT with fresh-render identity properties removed.
Fresh renders allocate different region ids by design. Keep character,
face, display, and all non-runtime properties so the comparison still checks
the visible right-hand surface rather than merely checking that rendering
completed."
(let ((copy (copy-sequence text)))
(when (> (length copy) 0)
(remove-list-of-text-properties
0 (length copy)
ebox-playground-flex-resize-evaluator--runtime-properties
copy))
copy))
(defun ebox-playground-flex-resize-evaluator--check (name condition detail)
"Record a named check and return whether CONDITION passed."
(push (format "%s %s -- %s"
(if condition "PASS" "FAIL") name detail)
ebox-playground-flex-resize-evaluator--output-lines)
condition)
(defun ebox-playground-flex-resize-evaluator--settle-preview
(preview preview-window)
"Publish PREVIEW at its settled PREVIEW-WINDOW viewport before measuring.
The GUI frame can acquire its final pixel width after `ebox-dsl-render' has
mounted the split. This setup publication is outside the measured splitter
action, so the action starts from a self-consistent runtime generation rather
than charging initial frame settling to resize."
(let* ((width (ebox-playground--window-viewport-width preview-window))
(state (and (buffer-live-p preview)
(ebox--buffer-render-state preview)))
(published-width (and state (plist-get state :viewport-width))))
(unless (numberp width)
(error "flex preview has no settled viewport width"))
(when (not (equal width published-width))
(let ((report (ebox-rerender-buffer-with-context preview width)))
(unless (plist-get report :runtime-published)
(error "flex preview initial viewport was not published"))))
width))
(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."
(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)
(resize-advice nil)
(resize-count 0)
(resize-elapsed 0.0)
(resize-finished nil)
(completed-without-error nil)
(ebox-playground-flex-resize-evaluator--output-lines nil)
(checks nil))
(condition-case error-data
(progn
(delete-other-windows)
(setq source
(find-file ebox-playground-flex-resize-evaluator--fixture))
(ebox-dsl-mode)
(ebox-dsl-render)
(setq preview
(ebox-playground-flex-resize-evaluator--preview-buffer)
preview-window (get-buffer-window preview (selected-frame)))
(unless (and (buffer-live-p preview)
(window-live-p preview-window))
(error "flex preview was not mounted in a window"))
(redisplay t)
(sleep-for 0.2)
(ebox-playground-flex-resize-evaluator--settle-preview
preview preview-window)
(redisplay t)
(sleep-for 0.2)
(setq resize-advice
(lambda (original &rest arguments)
(setq resize-count (1+ resize-count))
(let ((start (float-time)))
(unwind-protect
(apply original arguments)
(setq resize-elapsed
(+ resize-elapsed (- (float-time) start)))
(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))
(redisplay t)
(sleep-for 0.01))
(let* ((elapsed (- (float-time) start))
(viewport-width
(ebox-playground--window-viewport-width preview-window))
(report (ebox-buffer-update-report preview))
(projection-kind (plist-get report :projection-kind))
(native-flex-calls
(and (boundp
'ebox-native-reflow--flex-geometry-call-count)
ebox-native-reflow--flex-geometry-call-count))
(line-widths
(ebox-playground-flex-resize-evaluator--line-widths
preview))
(background
(with-current-buffer preview
face-remapping-alist))
(max-line-width (if line-widths
(apply #'max line-widths)
0))
(fresh-rendered
(and (numberp viewport-width)
(let ((ebox-viewport-width viewport-width)
;; Compare the published visible scroll
;; window with a fresh render at the same
;; viewport height. Omitting this binding
;; rendered the entire lazy document and made
;; every correct retained resize look unequal.
(ebox-viewport-height
(or (plist-get
(ebox--buffer-render-state preview)
:viewport-height)
36)))
(ebox-render
(ebox-playground-view
ebox-playground-flex-resize-evaluator--fixture
viewport-width)))))
(fresh-render-matches-p
(and (stringp fresh-rendered)
(with-current-buffer preview
(equal-including-properties
(ebox-playground-flex-resize-evaluator--visual-text
(buffer-string))
(ebox-playground-flex-resize-evaluator--visual-text
fresh-rendered)))))
(tp-operations (plist-get report :tp-operation-count))
(projection-retained-p
(and (memq projection-kind
'(viewport-reflow
viewport-reflow-mixed-scroll))
(not (plist-get report :tp-full-root))
(not (plist-get report :tp-scope-fallback)))))
(setq checks
(list
(ebox-playground-flex-resize-evaluator--check
"preview mounted"
(and (buffer-live-p preview)
(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))
(ebox-playground-flex-resize-evaluator--check
"resize completed"
resize-finished
(format "elapsed=%.6fs" elapsed))
(ebox-playground-flex-resize-evaluator--check
"viewport was published"
(and (numberp viewport-width)
(not (equal old-width viewport-width))
(eq (plist-get report :constraint-source)
'viewport)
(plist-get report :runtime-published))
(format "viewport=%S strategy=%S"
viewport-width (plist-get report :strategy)))
(ebox-playground-flex-resize-evaluator--check
"retained viewport projection"
projection-retained-p
(format "projection=%S axes=%S reconciled=%S full-root=%S scope-fallback=%S"
projection-kind
(plist-get report :viewport-axes)
(plist-get report :reconciled-objects)
(plist-get report :tp-full-root)
(plist-get report :tp-scope-fallback)))
(ebox-playground-flex-resize-evaluator--check
"rendered output matches a fresh viewport render"
fresh-render-matches-p
(format "max-line-width=%d viewport=%S fresh-length=%S"
max-line-width viewport-width
(and fresh-rendered
(length fresh-rendered))))
(ebox-playground-flex-resize-evaluator--check
"canvas background is preserved"
(equal background
'((default (:background "#FAF7F0"))) )
(format "face-remapping=%S" background))
(ebox-playground-flex-resize-evaluator--check
"resize is millisecond-scale"
(<= elapsed
ebox-playground-flex-resize-evaluator-max-seconds)
(format "elapsed=%.6fs threshold=%.6fs"
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"
old-width viewport-width elapsed resize-elapsed
(plist-get report :strategy) projection-kind
(plist-get report :viewport-axes)
(plist-get report :reconciled-objects)
native-flex-calls tp-operations
(plist-get report :cache-hit-count)
(plist-get report :cache-miss-count))
ebox-playground-flex-resize-evaluator--output-lines)
(setq completed-without-error t)
(and resize-finished
(cl-every #'identity checks)))))
(error
(push (format "FAIL evaluator error -- %S" error-data)
ebox-playground-flex-resize-evaluator--output-lines)
nil)
(quit
(push "FAIL evaluator interrupted"
ebox-playground-flex-resize-evaluator--output-lines)
nil))
(when resize-advice
(advice-remove 'ebox-rerender-buffer-with-context resize-advice))
(when (buffer-live-p preview)
(kill-buffer preview))
(when (buffer-live-p source)
(kill-buffer source))
(let ((passed (and completed-without-error
(not (null checks))
(cl-every #'identity checks))))
(push (if passed
"FLEX-RESIZE-EVALUATOR PASS"
"FLEX-RESIZE-EVALUATOR FAIL")
ebox-playground-flex-resize-evaluator--output-lines)
(mapconcat #'identity
(nreverse ebox-playground-flex-resize-evaluator--output-lines)
"\n"))))
(provide 'ebox-playground-flex-resize-evaluator)
;;; ebox-playground-flex-resize-evaluator.el ends here