diff --git a/ebox.el b/ebox.el index 85e1cbf..3a25e57 100644 --- a/ebox.el +++ b/ebox.el @@ -4626,18 +4626,25 @@ When VIEWPORT-HEIGHT is non-nil, height values using `(viewport-height)' are resolved against that line count. This preserves node and region identity and routes viewport-dependent changes through dirty-set and patch-set execution before falling back to root rerender." - (let* ((state (ebox--buffer-render-state buffer)) - (old-viewport-width (and state - (plist-get state :viewport-width)))) - (unless (ebox-surface-buffer-mounted-p buffer) - (user-error "Ebox buffer has no mounted TP surface: %S" buffer)) - (ebox--cancel-buffer-runtime-prewarm buffer) - (prog1 - (ebox-surface-update-buffer-viewport - buffer viewport-width viewport-height) - (ebox--schedule-buffer-runtime-prewarm buffer nil t) - (ebox--schedule-buffer-reflow-cache-prewarm - buffer old-viewport-width viewport-width)))) + (let ((execute + (lambda () + (let* ((state (ebox--buffer-render-state buffer)) + (old-viewport-width + (and state (plist-get state :viewport-width)))) + (unless (ebox-surface-buffer-mounted-p buffer) + (user-error "Ebox buffer has no mounted TP surface: %S" buffer)) + (ebox--cancel-buffer-runtime-prewarm buffer) + (prog1 + (ebox-surface-update-buffer-viewport + buffer viewport-width viewport-height) + (ebox--schedule-buffer-runtime-prewarm buffer nil t) + (ebox--schedule-buffer-reflow-cache-prewarm + buffer old-viewport-width viewport-width)))))) + (if noninteractive + (ebox--with-render-gc + (funcall execute)) + (ebox--with-deferred-render-gc + (funcall execute))))) (defun ebox--rerender-buffer-preserving-runtime (buffer) "Rerender BUFFER from stored runtime state without rebuilding identity." diff --git a/tests/ebox-core-render-tests.el b/tests/ebox-core-render-tests.el index b551077..dbc04d6 100644 --- a/tests/ebox-core-render-tests.el +++ b/tests/ebox-core-render-tests.el @@ -3277,6 +3277,103 @@ (when (and buffer (buffer-live-p buffer)) (kill-buffer buffer))))) +(ert-deftest ebox-interactive-viewport-rerender-owns-deferred-render-gc () + "Keep GC outside an interactive viewport transaction until it settles." + (ebox-test--reset-runtime-state) + (let ((ebox-runtime-idle-prewarm nil) + (ebox-runtime-idle-reflow-cache-prewarm nil) + (buffer nil)) + (unwind-protect + (progn + (setq buffer + (ebox-render-to-buffer + (generate-new-buffer-name " *ebox-test*") + (ebox-create :width '(viewport) :content "resize"))) + (let ((noninteractive nil) + (gc-cons-threshold 1000) + (gc-cons-percentage 0.1) + (ebox-deferred-render-gc-cons-threshold 4000) + (ebox-render-gc-cons-percentage 0.8) + (ebox--deferred-render-gc-state nil) + (ebox--deferred-render-gc-timer nil) + (ebox--deferred-render-gc-depth 0) + (original-update + (symbol-function 'ebox-surface-update-buffer-viewport)) + observed-threshold + observed-depth + scheduled + (gc-count 0)) + (cl-letf (((symbol-function 'ebox-surface-update-buffer-viewport) + (lambda (&rest arguments) + (setq observed-threshold gc-cons-threshold + observed-depth ebox--deferred-render-gc-depth) + (apply original-update arguments))) + ((symbol-function 'run-at-time) + (lambda (delay _repeat function &rest arguments) + (setq scheduled (list delay function arguments)) + 'ebox-test-viewport-gc-timer)) + ((symbol-function 'timerp) + (lambda (value) + (eq value 'ebox-test-viewport-gc-timer))) + ((symbol-function 'cancel-timer) #'ignore) + ((symbol-function 'garbage-collect) + (lambda () (cl-incf gc-count)))) + (ebox-rerender-buffer-with-context buffer 320) + (should (= observed-threshold 4000)) + (should (= observed-depth 1)) + (should (= (plist-get (ebox--buffer-render-state buffer) + :viewport-width) + 320)) + (should (= ebox--deferred-render-gc-depth 0)) + (should ebox--deferred-render-gc-state) + (should (eq ebox--deferred-render-gc-timer + 'ebox-test-viewport-gc-timer)) + (should (= (car scheduled) 0.2)) + (apply (nth 1 scheduled) (nth 2 scheduled)) + (should (= gc-cons-threshold 1000)) + (should (= gc-cons-percentage 0.1)) + (should (= gc-count 1))))) + (ebox-test--reset-runtime-state) + (when (and buffer (buffer-live-p buffer)) + (kill-buffer buffer))))) + +(ert-deftest ebox-batch-viewport-rerender-uses-render-gc-boundary () + "Raise GC only during a batch viewport transaction and restore on return." + (ebox-test--reset-runtime-state) + (let ((ebox-runtime-idle-prewarm nil) + (ebox-runtime-idle-reflow-cache-prewarm nil) + (buffer nil)) + (unwind-protect + (progn + (setq buffer + (ebox-render-to-buffer + (generate-new-buffer-name " *ebox-test*") + (ebox-create :width '(viewport) :content "resize"))) + (let ((noninteractive t) + (gc-cons-threshold 1000) + (gc-cons-percentage 0.1) + (ebox-render-gc-cons-threshold 4000) + (ebox-render-gc-cons-percentage 0.8) + (original-update + (symbol-function 'ebox-surface-update-buffer-viewport)) + observed-threshold + observed-percentage) + (cl-letf (((symbol-function 'ebox-surface-update-buffer-viewport) + (lambda (&rest arguments) + (setq observed-threshold gc-cons-threshold + observed-percentage gc-cons-percentage) + (apply original-update arguments)))) + (ebox-rerender-buffer-with-context buffer 320)) + (should (= observed-threshold 4000)) + (should (= observed-percentage 0.8)) + (should (= gc-cons-threshold 1000)) + (should (= gc-cons-percentage 0.1)) + (should-not ebox--deferred-render-gc-state) + (should-not ebox--deferred-render-gc-timer))) + (ebox-test--reset-runtime-state) + (when (and buffer (buffer-live-p buffer)) + (kill-buffer buffer))))) + (ert-deftest ebox-deferred-render-gc-can-raise-an-active-burst-budget () "Measured layout pressure should only raise an active burst budget." (let ((gc-cons-threshold 1000) diff --git a/tests/ebox-surface-tests.el b/tests/ebox-surface-tests.el index 4674abc..6e05f92 100644 --- a/tests/ebox-surface-tests.el +++ b/tests/ebox-surface-tests.el @@ -59,7 +59,7 @@ (lambda (_window) (error "Window outer width must not be sampled"))) ((symbol-function 'window-body-height) - (lambda (_window) 31))) + (lambda (_window &optional _pixelwise) 31))) (let ((values (ebox-surface--context-values buffer nil nil))) (should (= 775 (plist-get values :viewport-width))) (should (= 31 (plist-get values :viewport-height))))) @@ -92,7 +92,7 @@ ((symbol-function 'window-pixel-width) (lambda (_window) 1430)) ((symbol-function 'window-body-height) - (lambda (_window) 62))) + (lambda (_window &optional _pixelwise) 62))) (let ((values (ebox-surface--context-values buffer nil nil))) (should (= 1428 (plist-get values :viewport-width))) (should (= 62 (plist-get values :viewport-height))))) @@ -130,7 +130,7 @@ (should pixelwise) 1400)) ((symbol-function 'window-body-height) - (lambda (candidate) + (lambda (candidate &optional _pixelwise) (should (eq candidate window)) 60))) (let ((values (ebox-surface--context-values buffer nil nil))) @@ -163,7 +163,7 @@ (should pixelwise) (if (eq window 'current-frame-window) 901 333))) ((symbol-function 'window-body-height) - (lambda (_window) 31))) + (lambda (_window &optional _pixelwise) 31))) (let ((values (ebox-surface--context-values buffer nil nil))) (should (= 899 (plist-get values :viewport-width))) (should (= 31 (plist-get values :viewport-height))))) @@ -188,7 +188,7 @@ ((symbol-function 'ebox-surface--window-content-width) (lambda (_window) 240)) ((symbol-function 'window-body-height) - (lambda (_window) 20)) + (lambda (_window &optional _pixelwise) 20)) ((symbol-function 'ebox-rerender-buffer-with-context) (lambda (target width height) (setq calls (list target width height))))